diff --git a/README.md b/README.md index b5165ee..06a8124 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ or in `appsettings.json` file: --- ### ClientIp -For `ClientIp` enricher you can configure the `x-forwarded-for` header if the proxy server uses a different header to forward the IP address. +`ClientIp` enricher reads client IP from `HttpContext.Connection.RemoteIpAddress`. Since version 2.1, for [security reasons](https://nvd.nist.gov/vuln/detail/CVE-2023-22474), it no longer reads the `x-forwarded-for` header. To handle forwarded headers, configure [ForwardedHeadersOptions](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-7.0#forwarded-headers-middleware-order). If you still want to log `x-forwarded-for`, you can use the `RequestHeader` enricher. ```csharp Log.Logger = new LoggerConfiguration() - .Enrich.WithClientIp(headerName: "CF-Connecting-IP") + .Enrich.WithClientIp() ... ``` or @@ -60,10 +60,7 @@ or "Using": [ "Serilog.Enrichers.ClientInfo" ], "Enrich": [ { - "Name": "WithClientIp", - "Args": { - "headerName": "CF-Connecting-IP" - } + "Name": "WithClientIp" } ], } diff --git a/Serilog.Enrichers.ClientInfo.sln b/Serilog.Enrichers.ClientInfo.sln index c894d1c..3935707 100644 --- a/Serilog.Enrichers.ClientInfo.sln +++ b/Serilog.Enrichers.ClientInfo.sln @@ -53,4 +53,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {785F0E90-8DC5-4003-AD7A-33DE806F3B3A} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/src/Serilog.Enrichers.ClientInfo/Accessors/HttpContextAccessor.cs b/src/Serilog.Enrichers.ClientInfo/Accessors/HttpContextAccessor.cs deleted file mode 100644 index 1e32ab0..0000000 --- a/src/Serilog.Enrichers.ClientInfo/Accessors/HttpContextAccessor.cs +++ /dev/null @@ -1,16 +0,0 @@ -#if NETFULL -using System.Web; - -namespace Serilog.Enrichers.ClientInfo.Accessors -{ - public interface IHttpContextAccessor - { - HttpContext HttpContext { get; } - } - - internal class HttpContextAccessor : IHttpContextAccessor - { - public HttpContext HttpContext => HttpContext.Current; - } -} -#endif \ No newline at end of file diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs index 579aa61..2bad2c3 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs @@ -1,14 +1,7 @@ -using Serilog.Core; +using Microsoft.AspNetCore.Http; +using Serilog.Core; using Serilog.Events; -#if NETFULL - -using Serilog.Enrichers.ClientInfo.Accessors; - -#else -using Microsoft.AspNetCore.Http; -#endif - namespace Serilog.Enrichers; /// @@ -19,6 +12,11 @@ public class ClientHeaderEnricher : ILogEventEnricher private readonly string _headerKey; private readonly IHttpContextAccessor _contextAccessor; + /// + /// Initializes a new instance of the class. + /// + /// The key of the header. + /// The name of the property. public ClientHeaderEnricher(string headerKey, string propertyName) : this(headerKey, propertyName, new HttpContextAccessor()) { @@ -27,7 +25,7 @@ public ClientHeaderEnricher(string headerKey, string propertyName) internal ClientHeaderEnricher(string headerKey, string propertyName, IHttpContextAccessor contextAccessor) { _headerKey = headerKey; - _propertyName = string.IsNullOrWhiteSpace(propertyName) + _propertyName = string.IsNullOrWhiteSpace(propertyName) ? headerKey.Replace("-", "") : propertyName; _clientHeaderItemKey = $"Serilog_{headerKey}"; @@ -43,7 +41,9 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var httpContext = _contextAccessor.HttpContext; if (httpContext == null) + { return; + } if (httpContext.Items[_clientHeaderItemKey] is LogEventProperty logEventProperty) { @@ -59,4 +59,4 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) logEvent.AddPropertyIfAbsent(logProperty); } -} +} \ No newline at end of file diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs index 26aa083..bff7982 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs @@ -1,89 +1,55 @@ -using Serilog.Core; +using Microsoft.AspNetCore.Http; +using Serilog.Core; using Serilog.Events; -using System.Linq; using System.Runtime.CompilerServices; -#if NETFULL +[assembly: InternalsVisibleTo("Serilog.Enrichers.ClientInfo.Tests")] -using Serilog.Enrichers.ClientInfo.Accessors; +namespace Serilog.Enrichers; -#else -using Microsoft.AspNetCore.Http; -#endif +public class ClientIpEnricher : ILogEventEnricher +{ + private const string IpAddressPropertyName = "ClientIp"; + private const string IpAddressItemKey = "Serilog_ClientIp"; -[assembly: InternalsVisibleTo("Serilog.Enrichers.ClientInfo.Tests")] + private readonly IHttpContextAccessor _contextAccessor; -namespace Serilog.Enrichers -{ - public class ClientIpEnricher : ILogEventEnricher + /// + /// Initializes a new instance of the class. + /// + public ClientIpEnricher() : this(new HttpContextAccessor()) { - private const string IpAddressPropertyName = "ClientIp"; - private const string IpAddressItemKey = "Serilog_ClientIp"; - private readonly string _forwardHeaderKey; + } - private readonly IHttpContextAccessor _contextAccessor; + internal ClientIpEnricher(IHttpContextAccessor contextAccessor) + { + _contextAccessor = contextAccessor; + } - public ClientIpEnricher(string forwardHeaderKey) - : this(forwardHeaderKey, new HttpContextAccessor()) + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + var httpContext = _contextAccessor.HttpContext; + if (httpContext == null) { + return; } - internal ClientIpEnricher(string forwardHeaderKey, IHttpContextAccessor contextAccessor) + if (httpContext.Items[IpAddressItemKey] is LogEventProperty logEventProperty) { - _forwardHeaderKey = forwardHeaderKey; - _contextAccessor = contextAccessor; + logEvent.AddPropertyIfAbsent(logEventProperty); + return; } - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) - { - var httpContext = _contextAccessor.HttpContext; - if (httpContext == null) - return; - - if (httpContext.Items[IpAddressItemKey] is LogEventProperty logEventProperty) - { - logEvent.AddPropertyIfAbsent(logEventProperty); - return; - } - - var ipAddress = GetIpAddress(); - - if (string.IsNullOrWhiteSpace(ipAddress)) - ipAddress = "unknown"; + var ipAddress = _contextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); - var ipAddressProperty = new LogEventProperty(IpAddressPropertyName, new ScalarValue(ipAddress)); - httpContext.Items.Add(IpAddressItemKey, ipAddressProperty); - - logEvent.AddPropertyIfAbsent(ipAddressProperty); - } - -#if NETFULL - - private string GetIpAddress() + if (string.IsNullOrWhiteSpace(ipAddress)) { - var ipAddress = _contextAccessor.HttpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; - - return !string.IsNullOrEmpty(ipAddress) - ? GetIpAddressFromProxy(ipAddress) - : _contextAccessor.HttpContext.Request.ServerVariables["REMOTE_ADDR"]; + ipAddress = "unknown"; } -#else - private string GetIpAddress() - { - var ipAddress = _contextAccessor.HttpContext?.Request?.Headers[_forwardHeaderKey].FirstOrDefault(); + var ipAddressProperty = new LogEventProperty(IpAddressPropertyName, new ScalarValue(ipAddress)); + httpContext.Items.Add(IpAddressItemKey, ipAddressProperty); - return !string.IsNullOrEmpty(ipAddress) - ? GetIpAddressFromProxy(ipAddress) - : _contextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); - } -#endif - - private string GetIpAddressFromProxy(string proxifiedIpList) - { - var addresses = proxifiedIpList.Split(','); - - return addresses.Length == 0 ? string.Empty : addresses[0].Trim(); - } + logEvent.AddPropertyIfAbsent(ipAddressProperty); } } \ No newline at end of file diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs index d3e042a..dfa82d1 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs @@ -1,15 +1,8 @@ -using Serilog.Core; +using Microsoft.AspNetCore.Http; +using Serilog.Core; using Serilog.Events; using System; -#if NETFULL - -using Serilog.Enrichers.ClientInfo.Accessors; - -#else -using Microsoft.AspNetCore.Http; -#endif - namespace Serilog.Enrichers; /// @@ -21,6 +14,15 @@ public class CorrelationIdEnricher : ILogEventEnricher private readonly bool _addValueIfHeaderAbsence; private readonly IHttpContextAccessor _contextAccessor; + /// + /// Initializes a new instance of the class. + /// + /// + /// The header key used to retrieve the correlation ID from the HTTP request or response headers. + /// + /// + /// Determines whether to add a new correlation ID value if the header is absent. + /// public CorrelationIdEnricher(string headerKey, bool addValueIfHeaderAbsence) : this(headerKey, addValueIfHeaderAbsence, new HttpContextAccessor()) { @@ -33,6 +35,7 @@ internal CorrelationIdEnricher(string headerKey, bool addValueIfHeaderAbsence, I _contextAccessor = contextAccessor; } + /// public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var httpContext = _contextAccessor.HttpContext; @@ -47,10 +50,27 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) return; } - var header = httpContext.Request.Headers[_headerKey].ToString(); - var correlationId = !string.IsNullOrWhiteSpace(header) - ? header - : (_addValueIfHeaderAbsence ? Guid.NewGuid().ToString() : null); + var requestHeader = httpContext.Request.Headers[_headerKey]; + var responseHeader = httpContext.Response.Headers[_headerKey]; + + string correlationId; + + if (!string.IsNullOrWhiteSpace(requestHeader)) + { + correlationId = requestHeader; + } + else if (!string.IsNullOrWhiteSpace(responseHeader)) + { + correlationId = responseHeader; + } + else if (_addValueIfHeaderAbsence) + { + correlationId = Guid.NewGuid().ToString(); + } + else + { + correlationId = null; + } var correlationIdProperty = new LogEventProperty(PropertyName, new ScalarValue(correlationId)); logEvent.AddOrUpdateProperty(correlationIdProperty); diff --git a/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs b/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs index 8239a05..a2b243a 100644 --- a/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs +++ b/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs @@ -1,15 +1,7 @@ -using Serilog.Configuration; +using Microsoft.AspNetCore.Http; +using Serilog.Configuration; using Serilog.Enrichers; using System; -using System.Web; - -#if NETFULL - -using Serilog.Enrichers.ClientInfo.Accessors; - -#else -using Microsoft.AspNetCore.Http; -#endif namespace Serilog; @@ -38,7 +30,7 @@ public static LoggerConfiguration WithClientIp( throw new ArgumentNullException(nameof(enrichmentConfiguration)); } - return enrichmentConfiguration.With(new ClientIpEnricher(headerName)); + return enrichmentConfiguration.With(); } /// diff --git a/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj b/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj index 85d1699..ed86c55 100644 --- a/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj +++ b/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj @@ -1,7 +1,7 @@  - net462;netstandard2.0;netstandard2.1;net6.0;net7.0 + net6.0;net7.0;net8.0 Serilog.Enrichers.ClientInfo Serilog latest @@ -12,30 +12,11 @@ 2.0.3 - - NETCORE;NETSTANDARD;NETSTANDARD2_0 - - - NETCORE;NETSTANDARD;NETSTANDARD2_1 - - - NET45;NETFULL - + + + - - - - - - - - - - - - - - + diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs index 186d60a..674efcc 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs @@ -8,7 +8,6 @@ namespace Serilog.Enrichers.ClientInfo.Tests; public class ClientIpEnricherTests { - private const string ForwardHeaderKey = "x-forwarded-for"; private readonly IHttpContextAccessor _contextAccessor; public ClientIpEnricherTests() @@ -29,7 +28,7 @@ public void EnrichLogWithClientIp_ShouldCreateClientIpPropertyWithValue(string i var ipAddress = IPAddress.Parse(ip); _contextAccessor.HttpContext.Connection.RemoteIpAddress = ipAddress; - var ipEnricher = new ClientIpEnricher(ForwardHeaderKey, _contextAccessor); + var ipEnricher = new ClientIpEnricher(_contextAccessor); LogEvent evt = null; var log = new LoggerConfiguration() @@ -51,7 +50,7 @@ public void EnrichLogWithClientIp_WhenLogMoreThanOnce_ShouldReadClientIpValueFro { //Arrange _contextAccessor.HttpContext.Connection.RemoteIpAddress = IPAddress.Loopback; - var ipEnricher = new ClientIpEnricher(ForwardHeaderKey, _contextAccessor); + var ipEnricher = new ClientIpEnricher(_contextAccessor); LogEvent evt = null; var log = new LoggerConfiguration() @@ -69,60 +68,6 @@ public void EnrichLogWithClientIp_WhenLogMoreThanOnce_ShouldReadClientIpValueFro Assert.Equal(IPAddress.Loopback.ToString(), evt.Properties["ClientIp"].LiteralValue()); } - [Theory] - [InlineData("::1")] - [InlineData("192.168.1.1")] - [InlineData("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] - [InlineData("2001:db8:85a3:8d3:1319:8a2e:370:7348")] - public void EnrichLogWithClientIp_WhenRequestContainForwardHeader_ShouldCreateClientIpPropertyWithValue(string ip) - { - //Arrange - var ipAddress = IPAddress.Parse(ip); - _contextAccessor.HttpContext.Connection.RemoteIpAddress = IPAddress.Loopback; - _contextAccessor.HttpContext.Request.Headers.Add(ForwardHeaderKey, ipAddress.ToString()); - - var ipEnricher = new ClientIpEnricher(ForwardHeaderKey, _contextAccessor); - - LogEvent evt = null; - var log = new LoggerConfiguration() - .Enrich.With(ipEnricher) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - // Act - log.Information(@"Has an IP property"); - - // Assert - Assert.NotNull(evt); - Assert.True(evt.Properties.ContainsKey("ClientIp")); - Assert.Equal(ipAddress.ToString(), evt.Properties["ClientIp"].LiteralValue()); - } - - [Fact] - public void EnrichLogWithClientIp_WithCustomForwardHeaderAndRequest_ShouldCreateClientIpPropertyWithValue() - { - //Arrange - const string customForwardHeader = "CustomForwardHeader"; - _contextAccessor.HttpContext.Connection.RemoteIpAddress = IPAddress.Loopback; - _contextAccessor.HttpContext.Request.Headers.Add(customForwardHeader, IPAddress.Broadcast.ToString()); - - var ipEnricher = new ClientIpEnricher(customForwardHeader, _contextAccessor); - - LogEvent evt = null; - var log = new LoggerConfiguration() - .Enrich.With(ipEnricher) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); - - // Act - log.Information(@"Has an IP property"); - - // Assert - Assert.NotNull(evt); - Assert.True(evt.Properties.ContainsKey("ClientIp")); - Assert.Equal(IPAddress.Broadcast.ToString(), evt.Properties["ClientIp"].LiteralValue()); - } - [Fact] public void WithClientIp_ThenLoggerIsCalled_ShouldNotThrowException() { diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs index ae35f1d..093dc01 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs @@ -108,6 +108,54 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestNotContainCorrelationHeade Assert.NotNull(evt.Properties[LogPropertyName].LiteralValue().ToString()); } + [Fact] + public void EnrichLogWithCorrelationId_WhenHttpResponseContainsCorrelationIdHeader_ShouldCreateCorrelationIdProperty() + { + // Arrange + var correlationId = Guid.NewGuid().ToString(); + _contextAccessor.HttpContext.Response.Headers.Add(HeaderKey, correlationId); + var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); + + LogEvent evt = null; + var log = new LoggerConfiguration() + .Enrich.With(correlationIdEnricher) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + // Act + log.Information(@"Has a correlation id."); + + // Assert + Assert.NotNull(evt); + Assert.True(evt.Properties.ContainsKey(LogPropertyName)); + Assert.Equal(correlationId, evt.Properties[LogPropertyName].LiteralValue().ToString()); + } + + [Fact] + public void EnrichLogWithCorrelationId_WhenHttpRequestAndResponseContainCorrelationIdHeader_ShouldCreateCorrelationIdPropertyFromHttpRequest() + { + // Arrange + var requestCorrelationId = Guid.NewGuid().ToString(); + var responseCorrelationId = Guid.NewGuid().ToString(); + _contextAccessor.HttpContext.Request.Headers.Add(HeaderKey, requestCorrelationId); + _contextAccessor.HttpContext.Response.Headers.Add(HeaderKey, responseCorrelationId); + var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); + + LogEvent evt = null; + var log = new LoggerConfiguration() + .Enrich.With(correlationIdEnricher) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + // Act + log.Information(@"Has a correlation id."); + + // Assert + Assert.NotNull(evt); + Assert.True(evt.Properties.ContainsKey(LogPropertyName)); + Assert.Equal(requestCorrelationId, evt.Properties[LogPropertyName].LiteralValue().ToString()); + } + [Fact] public void WithClientIp_ThenLoggerIsCalled_ShouldNotThrowException() { diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj b/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj index 2266178..6974c71 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj +++ b/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj @@ -1,19 +1,19 @@  - net7.0 + net8.0 false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all