-
Notifications
You must be signed in to change notification settings - Fork 26
Allow customizing ClientIP property name for OpenTelemetry conventions #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: mo-esmp <[email protected]>
Co-authored-by: mo-esmp <[email protected]>
Co-authored-by: mo-esmp <[email protected]>
There was a problem hiding this 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.
| 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()); | ||
| } |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| { | ||
| ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName)); | ||
| _contextAccessor = new HttpContextAccessor(); | ||
| _ipVersionPreference = ipVersionPreference; | ||
| _ipAddressPropertyName = ipAddressPropertyName; |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| { | |
| ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName)); | |
| _contextAccessor = new HttpContextAccessor(); | |
| _ipVersionPreference = ipVersionPreference; | |
| _ipAddressPropertyName = ipAddressPropertyName; | |
| : this(new HttpContextAccessor(), ipVersionPreference, ipAddressPropertyName) | |
| { | |
| ArgumentNullException.ThrowIfNull(ipAddressPropertyName, nameof(ipAddressPropertyName)); |
| 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
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| 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)); | |
| } |
|
|
||
| // Assert | ||
| Assert.NotNull(evt); | ||
| Assert.True(evt.Properties.ContainsKey(customPropertyName)); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| Assert.True(evt.Properties.ContainsKey(propertyName)); | ||
| Assert.Equal(ipAddress.ToString(), evt.Properties[propertyName].LiteralValue()); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| 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()); |
| Assert.True(evt.Properties.ContainsKey(customPropertyName)); | ||
| Assert.Equal(IPAddress.Loopback.ToString(), evt.Properties[customPropertyName].LiteralValue()); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
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.
| 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()); |
The ClientIP enricher hardcoded the property name as "ClientIp", preventing alignment with OpenTelemetry semantic conventions which recommend "client.address".
Changes
ipAddressPropertyNameparameter to constructor with default "ClientIp" for backward compatibilityIpVersionPreferenceUsage
Or via appsettings.json:
{ "Serilog": { "Enrich": [{ "Name": "WithClientIp", "Args": { "ipVersionPreference": "Ipv4Only", "ipAddressPropertyName": "client.address" } }] } }Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.