Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 10, 2025

The ClientIP enricher hardcoded the property name as "ClientIp", preventing alignment with OpenTelemetry semantic conventions which recommend "client.address".

Changes

  • ClientIpEnricher: Added ipAddressPropertyName parameter to constructor with default "ClientIp" for backward compatibility
  • Extension methods: Added overloads accepting custom property name, both standalone and combined with IpVersionPreference
  • Validation: ArgumentNullException on null property names
  • Tests: Coverage for custom property names, caching behavior, and null validation
  • Documentation: Usage examples including OpenTelemetry convention

Usage

// OpenTelemetry convention
Log.Logger = new LoggerConfiguration()
    .Enrich.WithClientIp("client.address")
    .CreateLogger();

// With IP version preference
Log.Logger = new LoggerConfiguration()
    .Enrich.WithClientIp(IpVersionPreference.Ipv4Only, "client.address")
    .CreateLogger();

Or via appsettings.json:

{
  "Serilog": {
    "Enrich": [{
      "Name": "WithClientIp",
      "Args": {
        "ipVersionPreference": "Ipv4Only",
        "ipAddressPropertyName": "client.address"
      }
    }]
  }
}
Original prompt

This section details on the original issue you should resolve

<issue_title>Allow Renaming ClientIP Log Property Name</issue_title>
<issue_description>Right now the ClientIp enricher adds the client IP address with the property name of ClientIp. The OpenTelemetry semantic conventions recommends client.address. Would it be appropriate to add a constructor to the ClientIpEnricher to support using a custom name for the log property? Right now it is a const IpAddressPropertyName.

/// <inheritdoc />
public class ClientIpEnricher : ILogEventEnricher
{
    private readonly string _ipAddressPropertyName;

...

    internal ClientIpEnricher(IHttpContextAccessor contextAccessor,
        IpVersionPreference ipVersionPreference = IpVersionPreference.None,
        string ipAddressPropertyName = "ClientIp")
    {
        _contextAccessor = contextAccessor;
        _ipVersionPreference = ipVersionPreference;
        _ipAddressPropertyName = ipAddressPropertyName;
    }
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Add constructor to support renaming ClientIp log property Allow customizing ClientIP property name for OpenTelemetry conventions Dec 10, 2025
Copilot AI requested a review from mo-esmp December 10, 2025 07:27
@mo-esmp mo-esmp marked this pull request as ready for review December 10, 2025 07:30
Copilot AI review requested due to automatic review settings December 10, 2025 07:30
@mo-esmp mo-esmp merged commit bbc52de into master Dec 10, 2025
5 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the ability to customize the property name used by the ClientIpEnricher to log IP addresses, enabling alignment with OpenTelemetry semantic conventions which recommend client.address instead of the hardcoded ClientIp.

Key Changes:

  • Added constructor overloads accepting custom property names with backward-compatible defaults
  • Extended configuration API with new overload methods supporting custom property names
  • Included comprehensive test coverage for the new functionality

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs Added _ipAddressPropertyName field and new constructor accepting custom property name parameter; updated property creation to use configurable name
src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs Added two new WithClientIp overloads accepting custom property name, with and without IP version preference
test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs Added comprehensive tests for custom property names, including validation, caching, and integration tests
README.md Added documentation section explaining custom property name usage with code examples for both programmatic and JSON configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +203 to +224
public void EnrichLogWithClientIp_WithCustomPropertyName_WhenLogMoreThanOnce_ShouldCacheCorrectly()
{
// Arrange
const string customPropertyName = "client.address";
_contextAccessor.HttpContext.Connection.RemoteIpAddress = IPAddress.Loopback;
ClientIpEnricher ipEnricher = new(_contextAccessor, IpVersionPreference.None, customPropertyName);

LogEvent evt = null;
Logger log = new LoggerConfiguration()
.Enrich.With(ipEnricher)
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

// Act
log.Information("First log with custom property");
log.Information("Second log with custom property");

// Assert
Assert.NotNull(evt);
Assert.True(evt.Properties.ContainsKey(customPropertyName));
Assert.Equal(IPAddress.Loopback.ToString(), evt.Properties[customPropertyName].LiteralValue());
}
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caching test doesn't cover the scenario where multiple ClientIpEnricher instances with different custom property names are used in the same logger configuration. This is important to test since the caching mechanism uses a shared key (IpAddressItemKey) which could cause property name conflicts.

Consider adding a test that creates a logger with two enrichers using different property names (e.g., "ClientIp" and "client.address") and verifies that both properties are correctly added to log events.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +45
{
ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));
_contextAccessor = new HttpContextAccessor();
_ipVersionPreference = ipVersionPreference;
_ipAddressPropertyName = ipAddressPropertyName;
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public constructor ClientIpEnricher(IpVersionPreference, string) creates a new HttpContextAccessor() instance directly, while the existing public constructors delegate to the internal constructor that accepts an IHttpContextAccessor parameter. This inconsistency makes it harder to test and doesn't follow dependency injection best practices.

Consider delegating to the internal constructor instead:

public ClientIpEnricher(IpVersionPreference ipVersionPreference, string ipAddressPropertyName)
    : this(new HttpContextAccessor(), ipVersionPreference, ipAddressPropertyName)
{
    ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));
}

This approach maintains consistency with the existing constructors and makes the class more testable.

Suggested change
{
ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));
_contextAccessor = new HttpContextAccessor();
_ipVersionPreference = ipVersionPreference;
_ipAddressPropertyName = ipAddressPropertyName;
: this(new HttpContextAccessor(), ipVersionPreference, ipAddressPropertyName)
{
ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +63
public static LoggerConfiguration WithClientIp(
this LoggerEnrichmentConfiguration enrichmentConfiguration,
string ipAddressPropertyName)
{
ArgumentNullException.ThrowIfNull(enrichmentConfiguration, nameof(enrichmentConfiguration));
ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));

return enrichmentConfiguration.With(new ClientIpEnricher(IpVersionPreference.None, ipAddressPropertyName));
}
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method overload with only ipAddressPropertyName has an ambiguous signature that could conflict with potential future overloads. When a single string parameter is passed, it's not immediately clear whether it represents a property name or some other string parameter.

Consider reordering the parameters to match the pattern used in the three-parameter overload (line 75-78), where ipVersionPreference comes before ipAddressPropertyName. This would make the API more consistent and prevent ambiguity. Alternatively, make ipVersionPreference a required parameter for all custom property name overloads.

Suggested change
public static LoggerConfiguration WithClientIp(
this LoggerEnrichmentConfiguration enrichmentConfiguration,
string ipAddressPropertyName)
{
ArgumentNullException.ThrowIfNull(enrichmentConfiguration, nameof(enrichmentConfiguration));
ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName));
return enrichmentConfiguration.With(new ClientIpEnricher(IpVersionPreference.None, ipAddressPropertyName));
}

Copilot uses AI. Check for mistakes.

// Assert
Assert.NotNull(evt);
Assert.True(evt.Properties.ContainsKey(customPropertyName));
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +163
Assert.True(evt.Properties.ContainsKey(propertyName));
Assert.Equal(ipAddress.ToString(), evt.Properties[propertyName].LiteralValue());
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Suggested change
Assert.True(evt.Properties.ContainsKey(propertyName));
Assert.Equal(ipAddress.ToString(), evt.Properties[propertyName].LiteralValue());
Assert.True(evt.Properties.TryGetValue(propertyName, out var value));
Assert.Equal(ipAddress.ToString(), value.LiteralValue());

Copilot uses AI. Check for mistakes.
Comment on lines +222 to +223
Assert.True(evt.Properties.ContainsKey(customPropertyName));
Assert.Equal(IPAddress.Loopback.ToString(), evt.Properties[customPropertyName].LiteralValue());
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Suggested change
Assert.True(evt.Properties.ContainsKey(customPropertyName));
Assert.Equal(IPAddress.Loopback.ToString(), evt.Properties[customPropertyName].LiteralValue());
Assert.True(evt.Properties.TryGetValue(customPropertyName, out var propertyValue));
Assert.Equal(IPAddress.Loopback.ToString(), propertyValue.LiteralValue());

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow Renaming ClientIP Log Property Name

2 participants