Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion sample/SampleWebApp/SampleWebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@

<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Serilog.Enrichers.ClientInfo\Serilog.Enrichers.ClientInfo.csproj" />
</ItemGroup>
</Project>
3 changes: 2 additions & 1 deletion sample/SampleWebApp/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
{
"Name": "WithCorrelationId",
"Args": {
"addValueIfHeaderAbsence": true
"addValueIfHeaderAbsence": true,
"addCorrelationIdToResponse": true
}
},
{
Expand Down
78 changes: 63 additions & 15 deletions src/Serilog.Enrichers.ClientInfo/Enrichers/CorrelationIdEnricher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,15 +25,30 @@ public class CorrelationIdEnricher : ILogEventEnricher
/// <param name="addValueIfHeaderAbsence">
/// Determines whether to add a new correlation ID value if the header is absent.
/// </param>
public CorrelationIdEnricher(string headerKey, bool addValueIfHeaderAbsence)
: this(headerKey, addValueIfHeaderAbsence, new HttpContextAccessor())
/// <param name="addCorrelationIdToResponse">
/// Determines whether to add correlation ID value to <see cref="HttpContext.Response" /> header collection.
/// </param>
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;
}

Expand All @@ -57,24 +73,56 @@ 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);

httpContext.Items.Add(CorrelationIdItemKey, correlationIdProperty);
httpContext.Items.Add(Constants.CorrelationIdValueKey, correlationId);
}

private void AddCorrelationIdToResponse(HttpContext httpContext, in string correlationId)
Copy link

Copilot AI Oct 23, 2025

Choose a reason for hiding this comment

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

The in modifier on the correlationId parameter is unnecessary and provides no benefit for string types. Since strings are immutable reference types, using in doesn't prevent copying or improve performance. Remove the in modifier.

Suggested change
private void AddCorrelationIdToResponse(HttpContext httpContext, in string correlationId)
private void AddCorrelationIdToResponse(HttpContext httpContext, string correlationId)

Copilot uses AI. Check for mistakes.
{
if (_addCorrelationIdToResponse
&& !httpContext.Response.Headers.ContainsKey(_headerKey))
{
httpContext.Response.Headers[_headerKey] = correlationId;
}
}

private string PrepareCorrelationId(HttpContext httpContext)
{
StringValues requestHeader = httpContext.Request.Headers[_headerKey];
string returnValue;

if (!string.IsNullOrWhiteSpace(requestHeader))
{
returnValue = requestHeader;
}
else
{
StringValues responseHeader = httpContext.Response.Headers[_headerKey];

if (!string.IsNullOrWhiteSpace(responseHeader))
{
returnValue = responseHeader;
}
else
{
if (_addValueIfHeaderAbsence)
{
returnValue = Guid.NewGuid().ToString();
}
else
{
returnValue = null;
}
}
}

return returnValue;
Comment on lines +99 to +126
Copy link

Copilot AI Oct 23, 2025

Choose a reason for hiding this comment

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

The nested if-else statements create unnecessary complexity. The inner else block (lines 114-123) can be flattened by combining the conditions or using early returns in the method to improve readability.

Suggested change
string returnValue;
if (!string.IsNullOrWhiteSpace(requestHeader))
{
returnValue = requestHeader;
}
else
{
StringValues responseHeader = httpContext.Response.Headers[_headerKey];
if (!string.IsNullOrWhiteSpace(responseHeader))
{
returnValue = responseHeader;
}
else
{
if (_addValueIfHeaderAbsence)
{
returnValue = Guid.NewGuid().ToString();
}
else
{
returnValue = null;
}
}
}
return returnValue;
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;

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,20 @@ public static LoggerConfiguration WithClientIp(
/// Add generated correlation id value if correlation id header not available in
/// <see cref="HttpContext" /> header collection.
/// </param>
/// <param name="addCorrelationIdToResponse">
/// Add correlation id value to <see cref="HttpContext.Response" /> header collection.
/// </param>
/// <exception cref="ArgumentNullException">enrichmentConfiguration</exception>
/// <returns>The logger configuration so that multiple calls can be chained.</returns>
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));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -65,14 +41,15 @@ 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]
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()
Expand All @@ -87,14 +64,15 @@ 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]
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()
Expand All @@ -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]
Expand All @@ -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()
Expand All @@ -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]
Expand All @@ -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()
Expand All @@ -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]
Expand All @@ -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()
Expand All @@ -182,14 +164,15 @@ public void GetCorrelationId_WhenHttpRequestContainCorrelationHeader_ShouldRetur
// Assert
Assert.NotNull(evt);
Assert.Equal(correlationId, retrievedCorrelationId);
Assert.Equal(correlationId, _contextAccessor.HttpContext!.Response.Headers[HeaderKey]);
}

[Fact]
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()
Expand All @@ -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 _));
}
Expand All @@ -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()
Expand All @@ -229,6 +214,7 @@ public void
// Assert
Assert.NotNull(evt);
Assert.Null(retrievedCorrelationId);
Assert.False(_contextAccessor.HttpContext!.Response.Headers.ContainsKey(HeaderKey));
}

[Fact]
Expand All @@ -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)
Expand All @@ -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]
Expand All @@ -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()
Expand All @@ -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]
Expand Down