From 190f8730ee15ab1e2d9012ab2e9629df407fe696 Mon Sep 17 00:00:00 2001 From: Mohsen Esmailpour Date: Wed, 31 Jul 2024 12:01:06 +0200 Subject: [PATCH 1/2] Fix issue #39. (#40) Fix issue #39. --- .../Enrichers/ClientHeaderEnricher.cs | 1 + .../Enrichers/ClientIpEnricher.cs | 19 +++++---- .../ClientHeaderEnricherTests.cs | 39 +++++++++--------- .../ClientIpEnricherIntegrationTests.cs | 34 +++++++++++++++ .../ClientIpEnricherTests.cs | 4 +- .../CorrelationIdEnricherTests.cs | 13 +++--- .../DelegatingSink.cs | 28 +++++-------- .../Extensions.cs | 5 +-- .../Serilog.Enrichers.ClientInfo.Tests.csproj | 2 + .../TestSetup/CustomWebApplicationFactory.cs | 13 ++++++ .../TestSetup/WebApi.cs | 41 +++++++++++++++++++ 11 files changed, 139 insertions(+), 60 deletions(-) create mode 100644 test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherIntegrationTests.cs create mode 100644 test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/CustomWebApplicationFactory.cs create mode 100644 test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/WebApi.cs diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs index 2bad2c3..33722be 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientHeaderEnricher.cs @@ -37,6 +37,7 @@ internal ClientHeaderEnricher(IHttpContextAccessor contextAccessor) _contextAccessor = contextAccessor; } + /// public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var httpContext = _contextAccessor.HttpContext; diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs index bff7982..b29f80c 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/ClientIpEnricher.cs @@ -7,6 +7,7 @@ namespace Serilog.Enrichers; +/// public class ClientIpEnricher : ILogEventEnricher { private const string IpAddressPropertyName = "ClientIp"; @@ -26,6 +27,7 @@ internal ClientIpEnricher(IHttpContextAccessor contextAccessor) _contextAccessor = contextAccessor; } + /// public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var httpContext = _contextAccessor.HttpContext; @@ -34,22 +36,21 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) return; } + var ipAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; + if (httpContext.Items[IpAddressItemKey] is LogEventProperty logEventProperty) { + if (!((ScalarValue)logEventProperty.Value).Value.ToString()!.Equals(ipAddress)) + { + logEventProperty = new LogEventProperty(IpAddressPropertyName, new ScalarValue(ipAddress)); + } + logEvent.AddPropertyIfAbsent(logEventProperty); return; } - var ipAddress = _contextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); - - if (string.IsNullOrWhiteSpace(ipAddress)) - { - ipAddress = "unknown"; - } - - var ipAddressProperty = new LogEventProperty(IpAddressPropertyName, new ScalarValue(ipAddress)); + LogEventProperty ipAddressProperty = new(IpAddressPropertyName, new ScalarValue(ipAddress)); httpContext.Items.Add(IpAddressItemKey, ipAddressProperty); - logEvent.AddPropertyIfAbsent(ipAddressProperty); } } \ No newline at end of file diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/ClientHeaderEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/ClientHeaderEnricherTests.cs index ac3f147..30554b0 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/ClientHeaderEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/ClientHeaderEnricherTests.cs @@ -16,7 +16,7 @@ public ClientHeaderEnricherTests() _contextAccessor = Substitute.For(); _contextAccessor.HttpContext.Returns(httpContext); } - + [Fact] public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateNamedHeaderValueProperty() { @@ -24,7 +24,7 @@ public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateN var headerKey = "RequestId"; var propertyName = "HttpRequestId"; var headerValue = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Request.Headers.Add(headerKey, headerValue); + _contextAccessor.HttpContext!.Request!.Headers[headerKey] = headerValue; var clientHeaderEnricher = new ClientHeaderEnricher(headerKey, propertyName, _contextAccessor); @@ -35,8 +35,8 @@ public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateN .CreateLogger(); // Act - log.Information(@"First testing log enricher."); - log.Information(@"Second testing log enricher."); + log.Information("First testing log enricher."); + log.Information("Second testing log enricher."); // Assert Assert.NotNull(evt); @@ -50,9 +50,9 @@ public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateH // Arrange var headerKey = "RequestId"; var headerValue = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Request.Headers.Add(headerKey, headerValue); + _contextAccessor!.HttpContext!.Request!.Headers[headerKey] = headerValue; - var clientHeaderEnricher = new ClientHeaderEnricher(headerKey, propertyName:string.Empty, _contextAccessor); + var clientHeaderEnricher = new ClientHeaderEnricher(headerKey, propertyName: string.Empty, _contextAccessor); LogEvent evt = null; var log = new LoggerConfiguration() @@ -61,8 +61,8 @@ public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateH .CreateLogger(); // Act - log.Information(@"First testing log enricher."); - log.Information(@"Second testing log enricher."); + log.Information("First testing log enricher."); + log.Information("Second testing log enricher."); // Assert Assert.NotNull(evt); @@ -71,18 +71,17 @@ public void EnrichLogWithClientHeader_WhenHttpRequestContainHeader_ShouldCreateH } [Fact] - public void EnrichLogWithMulitpleClientHeaderEnricher_WhenHttpRequestContainHeaders_ShouldCreateHeaderValuesProperty() + public void EnrichLogWithMultipleClientHeaderEnricher_WhenHttpRequestContainHeaders_ShouldCreateHeaderValuesProperty() { // Arrange var headerKey1 = "Header1"; var headerKey2 = "User-Agent"; var headerValue1 = Guid.NewGuid().ToString(); var headerValue2 = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Request.Headers.Add(headerKey1, headerValue1); - _contextAccessor.HttpContext.Request.Headers.Add(headerKey2, headerValue2); - - var clientHeaderEnricher1 = new ClientHeaderEnricher(headerKey1, propertyName:string.Empty, _contextAccessor); - var clientHeaderEnricher2 = new ClientHeaderEnricher(headerKey2, propertyName:string.Empty, _contextAccessor); + _contextAccessor!.HttpContext!.Request!.Headers[headerKey1] = headerValue1; + _contextAccessor!.HttpContext!.Request!.Headers[headerKey2] = headerValue2; + var clientHeaderEnricher1 = new ClientHeaderEnricher(headerKey1, propertyName: string.Empty, _contextAccessor); + var clientHeaderEnricher2 = new ClientHeaderEnricher(headerKey2, propertyName: string.Empty, _contextAccessor); LogEvent evt = null; var log = new LoggerConfiguration() @@ -92,8 +91,8 @@ public void EnrichLogWithMulitpleClientHeaderEnricher_WhenHttpRequestContainHead .CreateLogger(); // Act - log.Information(@"First testing log enricher."); - log.Information(@"Second testing log enricher."); + log.Information("First testing log enricher."); + log.Information("Second testing log enricher."); // Assert Assert.NotNull(evt); @@ -108,7 +107,7 @@ public void EnrichLogWithClientHeader_WhenHttpRequestNotContainHeader_ShouldCrea { // Arrange var headerKey = "RequestId"; - var clientHeaderEnricher = new ClientHeaderEnricher(headerKey, propertyName:string.Empty, _contextAccessor); + var clientHeaderEnricher = new ClientHeaderEnricher(headerKey, propertyName: string.Empty, _contextAccessor); LogEvent evt = null; var log = new LoggerConfiguration() @@ -117,8 +116,8 @@ public void EnrichLogWithClientHeader_WhenHttpRequestNotContainHeader_ShouldCrea .CreateLogger(); // Act - log.Information(@"First testing log enricher."); - log.Information(@"Second testing log enricher."); + log.Information("First testing log enricher."); + log.Information("Second testing log enricher."); // Assert Assert.NotNull(evt); @@ -132,7 +131,7 @@ public void WithRequestHeader_ThenLoggerIsCalled_ShouldNotThrowException() // Arrange var logger = new LoggerConfiguration() .Enrich.WithRequestHeader("HeaderName") - .WriteTo.Sink(new DelegatingSink(e => { })) + .WriteTo.Sink(new DelegatingSink(_ => { })) .CreateLogger(); // Act diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherIntegrationTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherIntegrationTests.cs new file mode 100644 index 0000000..dbcf118 --- /dev/null +++ b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherIntegrationTests.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Serilog.Enrichers.ClientInfo.Tests; + +public class ClientIpEnricherIntegrationTests(CustomWebApplicationFactory factory) : IClassFixture +{ + [Fact] + public async Task GetRoot_ReturnsHelloWorld() + { + // Arrange + const string ip = "1.2.3.4"; + + var client = factory.CreateClient(); + client.DefaultRequestHeaders.Add("x-forwarded-for", ip); + + // Act + var response = await client.GetAsync("/"); + var logs = DelegatingSink.Logs; + var allClientIpLogs = logs + .SelectMany(l => l.Properties) + .Where(p => p.Key == "ClientIp") + .ToList(); + var forwardedClientIpLogs = logs + .SelectMany(l => l.Properties) + .Where(p => p.Key == "ClientIp" && p.Value.LiteralValue().Equals(ip)) + .ToList(); + + // Assert + response.EnsureSuccessStatusCode(); + Assert.Equal(forwardedClientIpLogs.Count, allClientIpLogs.Count - 1); + } +} \ No newline at end of file diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs index 674efcc..a35d098 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/ClientIpEnricherTests.cs @@ -26,7 +26,7 @@ public void EnrichLogWithClientIp_ShouldCreateClientIpPropertyWithValue(string i { // Arrange var ipAddress = IPAddress.Parse(ip); - _contextAccessor.HttpContext.Connection.RemoteIpAddress = ipAddress; + _contextAccessor.HttpContext!.Connection.RemoteIpAddress = ipAddress; var ipEnricher = new ClientIpEnricher(_contextAccessor); @@ -74,7 +74,7 @@ public void WithClientIp_ThenLoggerIsCalled_ShouldNotThrowException() // Arrange var logger = new LoggerConfiguration() .Enrich.WithClientIp() - .WriteTo.Sink(new DelegatingSink(e => { })) + .WriteTo.Sink(new DelegatingSink(_ => { })) .CreateLogger(); // Act diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs index 093dc01..34c1793 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs @@ -24,7 +24,7 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestContainCorrelationHeader_S { // Arrange var correlationId = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Request.Headers.Add(HeaderKey, correlationId); + _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); LogEvent evt = null; @@ -47,8 +47,7 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestContainCorrelationHeader_S { // Arrange var correlationId = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Request.Headers.Add(HeaderKey, correlationId); - + _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); LogEvent evt = null; @@ -113,7 +112,7 @@ public void EnrichLogWithCorrelationId_WhenHttpResponseContainsCorrelationIdHead { // Arrange var correlationId = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext.Response.Headers.Add(HeaderKey, correlationId); + _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); LogEvent evt = null; @@ -137,8 +136,8 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestAndResponseContainCorrelat // 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); + _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = requestCorrelationId; + _contextAccessor.HttpContext!.Response!.Headers[HeaderKey] = responseCorrelationId; var correlationIdEnricher = new CorrelationIdEnricher(HeaderKey, false, _contextAccessor); LogEvent evt = null; @@ -162,7 +161,7 @@ public void WithClientIp_ThenLoggerIsCalled_ShouldNotThrowException() // Arrange var logger = new LoggerConfiguration() .Enrich.WithCorrelationId() - .WriteTo.Sink(new DelegatingSink(e => { })) + .WriteTo.Sink(new DelegatingSink(_ => { })) .CreateLogger(); // Act diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/DelegatingSink.cs b/test/Serilog.Enrichers.ClientInfo.Tests/DelegatingSink.cs index cae4470..e1e42f6 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/DelegatingSink.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/DelegatingSink.cs @@ -1,32 +1,24 @@ using Serilog.Core; using Serilog.Events; using System; +using System.Collections.Generic; namespace Serilog.Enrichers.ClientInfo.Tests; -public class DelegatingSink : ILogEventSink +public class DelegatingSink(Action write, bool saveLogs = false) : ILogEventSink { - private readonly Action _write; + private static readonly List LogsEvents = new(); + private readonly Action _write = write ?? throw new ArgumentNullException(nameof(write)); - public DelegatingSink(Action write) - { - _write = write ?? throw new ArgumentNullException(nameof(write)); - } + public static IReadOnlyList Logs => LogsEvents; public void Emit(LogEvent logEvent) { - _write(logEvent); - } + if (saveLogs) + { + LogsEvents.Add(logEvent); + } - public static LogEvent GetLogEvent(Action writeAction) - { - LogEvent result = null; - var l = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.Sink(new DelegatingSink(le => result = le)) - .CreateLogger(); - - writeAction(l); - return result; + _write(logEvent); } } \ No newline at end of file diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/Extensions.cs b/test/Serilog.Enrichers.ClientInfo.Tests/Extensions.cs index 652504c..4dab93d 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/Extensions.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/Extensions.cs @@ -4,8 +4,5 @@ namespace Serilog.Enrichers.ClientInfo.Tests; internal static class Extensions { - public static object LiteralValue(this LogEventPropertyValue @this) - { - return ((ScalarValue)@this).Value; - } + public static object LiteralValue(this LogEventPropertyValue @this) => ((ScalarValue)@this).Value; } \ No newline at end of file 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 6974c71..4c6e7a0 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj +++ b/test/Serilog.Enrichers.ClientInfo.Tests/Serilog.Enrichers.ClientInfo.Tests.csproj @@ -6,8 +6,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/CustomWebApplicationFactory.cs b/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/CustomWebApplicationFactory.cs new file mode 100644 index 0000000..77bd3a5 --- /dev/null +++ b/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/CustomWebApplicationFactory.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using System.IO; + +namespace Serilog.Enrichers.ClientInfo.Tests; + +public class CustomWebApplicationFactory : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseContentRoot(Directory.GetCurrentDirectory()); + } +} \ No newline at end of file diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/WebApi.cs b/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/WebApi.cs new file mode 100644 index 0000000..822b616 --- /dev/null +++ b/test/Serilog.Enrichers.ClientInfo.Tests/TestSetup/WebApi.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Enrichers.ClientInfo.Tests; +using Serilog.Events; + +Log.Logger = new LoggerConfiguration() + .Enrich.WithClientIp() + .Enrich.WithRequestHeader("X-Forwarded-For") + .WriteTo.Sink(new DelegatingSink(e => LogEvent = e, saveLogs: true)) + .CreateLogger(); + +var builder = WebApplication.CreateBuilder(args); +builder.Host.UseSerilog(); +builder.Services + .AddHttpContextAccessor() + .Configure( + options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); + +var app = builder.Build(); + +app.UseForwardedHeaders(); + +app.MapGet("/", () => "hello world"); + +app.Run(); + +public partial class Program +{ + private Program() + { } + + public static LogEvent LogEvent; +} \ No newline at end of file From 26c8fae981c5fd91fe62b53fffe3e95a171a6548 Mon Sep 17 00:00:00 2001 From: Mohsen Esmailpour Date: Fri, 2 Aug 2024 10:10:21 +0200 Subject: [PATCH 2/2] Update nuspec file. (#41) Update nuspec file. --- Serilog.Enrichers.ClientInfo.nuspec | 26 +++++++------------ Serilog.Enrichers.ClientInfo.sln | 3 ++- .../Serilog.Enrichers.ClientInfo.csproj | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Serilog.Enrichers.ClientInfo.nuspec b/Serilog.Enrichers.ClientInfo.nuspec index 56b8286..b797115 100644 --- a/Serilog.Enrichers.ClientInfo.nuspec +++ b/Serilog.Enrichers.ClientInfo.nuspec @@ -12,27 +12,20 @@ false Enrich logs with client IP, CorrelationId and HTTP request headers. docs\README.md - - + assets\icon.png serilog enrichers enricher ip correlation-id request header + © 2024 Serilog Contributors + - - - - - - - - - - - + + + @@ -41,14 +34,15 @@ + + + - - - + diff --git a/Serilog.Enrichers.ClientInfo.sln b/Serilog.Enrichers.ClientInfo.sln index 3935707..33635c9 100644 --- a/Serilog.Enrichers.ClientInfo.sln +++ b/Serilog.Enrichers.ClientInfo.sln @@ -14,6 +14,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{931596BA-056D-4E8C-9FED-6C60C0D1AB83}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore + icon.png = icon.png LICENSE = LICENSE README.md = README.md Serilog.Enrichers.ClientInfo.nuspec = Serilog.Enrichers.ClientInfo.nuspec @@ -53,4 +54,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {785F0E90-8DC5-4003-AD7A-33DE806F3B3A} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal diff --git a/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj b/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj index ed86c55..cf3244d 100644 --- a/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj +++ b/src/Serilog.Enrichers.ClientInfo/Serilog.Enrichers.ClientInfo.csproj @@ -9,7 +9,7 @@ true embedded true - 2.0.3 + 2.1.1