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
69 changes: 54 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,47 @@ 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, 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;
}
}
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