diff --git a/sample/SampleWebApp/SampleWebApp.csproj b/sample/SampleWebApp/SampleWebApp.csproj index 1d6aebc..95007a8 100644 --- a/sample/SampleWebApp/SampleWebApp.csproj +++ b/sample/SampleWebApp/SampleWebApp.csproj @@ -8,9 +8,12 @@ - + + + + diff --git a/sample/SampleWebApp/appsettings.json b/sample/SampleWebApp/appsettings.json index c21e1ca..09b7466 100644 --- a/sample/SampleWebApp/appsettings.json +++ b/sample/SampleWebApp/appsettings.json @@ -31,7 +31,8 @@ { "Name": "WithCorrelationId", "Args": { - "addValueIfHeaderAbsence": true + "addValueIfHeaderAbsence": true, + "addCorrelationIdToResponse": true } }, { diff --git a/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs b/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs index 9083eda..877f661 100644 --- a/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs +++ b/src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs @@ -11,6 +11,7 @@ public class CorrelationIdEnricher : ILogEventEnricher { private const string CorrelationIdItemKey = "Serilog_CorrelationId"; private const string PropertyName = "CorrelationId"; + private readonly bool _addCorrelationIdToResponse; private readonly bool _addValueIfHeaderAbsence; private readonly IHttpContextAccessor _contextAccessor; private readonly string _headerKey; @@ -24,15 +25,30 @@ public class CorrelationIdEnricher : ILogEventEnricher /// /// 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()) + /// + /// Determines whether to add correlation ID value to header collection. + /// + public CorrelationIdEnricher( + string headerKey, + bool addValueIfHeaderAbsence, + bool addCorrelationIdToResponse) + : this( + headerKey, + addValueIfHeaderAbsence, + addCorrelationIdToResponse, + new HttpContextAccessor()) { } - internal CorrelationIdEnricher(string headerKey, bool addValueIfHeaderAbsence, IHttpContextAccessor contextAccessor) + internal CorrelationIdEnricher( + string headerKey, + bool addValueIfHeaderAbsence, + bool addCorrelationIdToResponse, + IHttpContextAccessor contextAccessor) { _headerKey = headerKey; _addValueIfHeaderAbsence = addValueIfHeaderAbsence; + _addCorrelationIdToResponse = addCorrelationIdToResponse; _contextAccessor = contextAccessor; } @@ -57,19 +73,9 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) return; } - StringValues requestHeader = httpContext.Request.Headers[_headerKey]; - StringValues responseHeader = httpContext.Response.Headers[_headerKey]; + string correlationId = PrepareCorrelationId(httpContext); - 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; + AddCorrelationIdToResponse(httpContext, correlationId); LogEventProperty correlationIdProperty = new(PropertyName, new ScalarValue(correlationId)); logEvent.AddOrUpdateProperty(correlationIdProperty); @@ -77,4 +83,37 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) httpContext.Items.Add(CorrelationIdItemKey, correlationIdProperty); httpContext.Items.Add(Constants.CorrelationIdValueKey, correlationId); } + + private void AddCorrelationIdToResponse(HttpContext httpContext, string correlationId) + { + if (_addCorrelationIdToResponse + && !httpContext.Response.Headers.ContainsKey(_headerKey)) + { + httpContext.Response.Headers[_headerKey] = correlationId; + } + } + + private string PrepareCorrelationId(HttpContext httpContext) + { + StringValues requestHeader = httpContext.Request.Headers[_headerKey]; + + if (!string.IsNullOrWhiteSpace(requestHeader)) + { + return requestHeader; + } + + StringValues responseHeader = httpContext.Response.Headers[_headerKey]; + + if (!string.IsNullOrWhiteSpace(responseHeader)) + { + return responseHeader; + } + + if (_addValueIfHeaderAbsence) + { + return Guid.NewGuid().ToString(); + } + + return null; + } } \ No newline at end of file diff --git a/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs b/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs index e943688..bbcf8d2 100644 --- a/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs +++ b/src/Serilog.Enrichers.ClientInfo/Extensions/ClientInfoLoggerConfigurationExtensions.cs @@ -56,16 +56,20 @@ public static LoggerConfiguration WithClientIp( /// Add generated correlation id value if correlation id header not available in /// header collection. /// + /// + /// Add correlation id value to header collection. + /// /// enrichmentConfiguration /// The logger configuration so that multiple calls can be chained. public static LoggerConfiguration WithCorrelationId( this LoggerEnrichmentConfiguration enrichmentConfiguration, string headerName = "x-correlation-id", - bool addValueIfHeaderAbsence = false) + bool addValueIfHeaderAbsence = false, + bool addCorrelationIdToResponse = false) { ArgumentNullException.ThrowIfNull(enrichmentConfiguration, nameof(enrichmentConfiguration)); - return enrichmentConfiguration.With(new CorrelationIdEnricher(headerName, addValueIfHeaderAbsence)); + return enrichmentConfiguration.With(new CorrelationIdEnricher(headerName, addValueIfHeaderAbsence, addCorrelationIdToResponse)); } /// diff --git a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs index 77021e0..76b48c5 100644 --- a/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs +++ b/test/Serilog.Enrichers.ClientInfo.Tests/CorrelationIdEnricherTests.cs @@ -26,31 +26,7 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestContainCorrelationHeader_S // Arrange string correlationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); - - LogEvent evt = null; - Logger 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_WhenHttpRequestContainCorrelationHeader_ShouldCreateCorrelationIdPropertyHasValue() - { - // Arrange - string correlationId = Guid.NewGuid().ToString(); - _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -65,6 +41,7 @@ public void Assert.NotNull(evt); Assert.True(evt.Properties.ContainsKey(LogPropertyName)); Assert.Equal(correlationId, evt.Properties[LogPropertyName].LiteralValue().ToString()); + Assert.Equal(correlationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]); } [Fact] @@ -72,7 +49,7 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestNotContainCorrelationHeaderAndAddDefaultValueIsFalse_ShouldCreateCorrelationIdPropertyWithNoValue() { // Arrange - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, false, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -87,6 +64,7 @@ public void Assert.NotNull(evt); Assert.True(evt.Properties.ContainsKey(LogPropertyName)); Assert.Null(evt.Properties[LogPropertyName].LiteralValue()); + Assert.False(_contextAccessor.HttpContext!.Response.Headers.ContainsKey(HeaderKey)); } [Fact] @@ -94,7 +72,7 @@ public void EnrichLogWithCorrelationId_WhenHttpRequestNotContainCorrelationHeaderAndAddDefaultValueIsTrue_ShouldCreateCorrelationIdPropertyHasValue() { // Arrange - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, true, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, true, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -109,6 +87,8 @@ public void Assert.NotNull(evt); Assert.True(evt.Properties.ContainsKey(LogPropertyName)); Assert.NotNull(evt.Properties[LogPropertyName].LiteralValue().ToString()); + Assert.NotNull(_contextAccessor.HttpContext!.Response.Headers[HeaderKey]); + Assert.Equal(evt.Properties[LogPropertyName].LiteralValue(), _contextAccessor.HttpContext!.Response.Headers[HeaderKey].ToString()); } [Fact] @@ -118,7 +98,7 @@ public void // Arrange string correlationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -133,6 +113,7 @@ public void Assert.NotNull(evt); Assert.True(evt.Properties.ContainsKey(LogPropertyName)); Assert.Equal(correlationId, evt.Properties[LogPropertyName].LiteralValue().ToString()); + Assert.Equal(correlationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]); } [Fact] @@ -144,7 +125,7 @@ public void string responseCorrelationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = requestCorrelationId; _contextAccessor.HttpContext!.Response!.Headers[HeaderKey] = responseCorrelationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -159,6 +140,7 @@ public void Assert.NotNull(evt); Assert.True(evt.Properties.ContainsKey(LogPropertyName)); Assert.Equal(requestCorrelationId, evt.Properties[LogPropertyName].LiteralValue().ToString()); + Assert.Equal(responseCorrelationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]); } [Fact] @@ -167,7 +149,7 @@ public void GetCorrelationId_WhenHttpRequestContainCorrelationHeader_ShouldRetur // Arrange string correlationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -182,6 +164,7 @@ public void GetCorrelationId_WhenHttpRequestContainCorrelationHeader_ShouldRetur // Assert Assert.NotNull(evt); Assert.Equal(correlationId, retrievedCorrelationId); + Assert.Equal(correlationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]); } [Fact] @@ -189,7 +172,7 @@ public void GetCorrelationId_WhenHttpRequestNotContainCorrelationHeaderAndAddDefaultValueIsTrue_ShouldReturnGeneratedCorrelationIdFromHttpContext() { // Arrange - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, true, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, true, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -200,11 +183,13 @@ public void // Act log.Information("Has a correlation id."); string retrievedCorrelationId = _contextAccessor.HttpContext!.GetCorrelationId(); + string correlationIdFromResponse = _contextAccessor.HttpContext!.Response.Headers[HeaderKey]; // Assert Assert.NotNull(evt); Assert.NotNull(retrievedCorrelationId); Assert.NotEmpty(retrievedCorrelationId); + Assert.Equal(retrievedCorrelationId, correlationIdFromResponse); // Verify it's a valid GUID format Assert.True(Guid.TryParse(retrievedCorrelationId, out _)); } @@ -214,7 +199,7 @@ public void GetCorrelationId_WhenHttpRequestNotContainCorrelationHeaderAndAddDefaultValueIsFalse_ShouldReturnNullFromHttpContext() { // Arrange - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, false, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -229,6 +214,7 @@ public void // Assert Assert.NotNull(evt); Assert.Null(retrievedCorrelationId); + Assert.False(_contextAccessor.HttpContext!.Response.Headers.ContainsKey(HeaderKey)); } [Fact] @@ -237,7 +223,7 @@ public void GetCorrelationId_WhenCalledMultipleTimes_ShouldReturnSameCorrelation // Arrange string correlationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, false, _contextAccessor); Logger log = new LoggerConfiguration() .Enrich.With(correlationIdEnricher) @@ -255,6 +241,7 @@ public void GetCorrelationId_WhenCalledMultipleTimes_ShouldReturnSameCorrelation Assert.Equal(correlationId, firstRetrieval); Assert.Equal(correlationId, secondRetrieval); Assert.Equal(firstRetrieval, secondRetrieval); + Assert.False(_contextAccessor.HttpContext!.Response.Headers.ContainsKey(HeaderKey)); } [Fact] @@ -273,7 +260,7 @@ public void EnrichLogWithCorrelationId_BackwardCompatibility_OldRetrievalMethodS // Arrange string correlationId = Guid.NewGuid().ToString(); _contextAccessor.HttpContext!.Request!.Headers[HeaderKey] = correlationId; - CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, _contextAccessor); + CorrelationIdEnricher correlationIdEnricher = new(HeaderKey, false, true, _contextAccessor); LogEvent evt = null; Logger log = new LoggerConfiguration() @@ -300,6 +287,7 @@ public void EnrichLogWithCorrelationId_BackwardCompatibility_OldRetrievalMethodS Assert.Equal(correlationId, retrievedCorrelationIdOldWay); Assert.Equal(correlationId, retrievedCorrelationIdNewWay); Assert.Equal(retrievedCorrelationIdOldWay, retrievedCorrelationIdNewWay); + Assert.Equal(correlationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]); } [Fact]