Correlationid in .net
What is it?
One of the worst things I can be asked to do is look at logs. Usually they come in a large text dump with time stamps and vauge messages. Trying to match up the time stamps and try and suss out what log is relevant to me.
Ignore poor logging practices for a second, one quick fix we can add to our application is correlation id logging. I'm going to be using serilog (along with serilog context logging) in this example, but you can google how other logging providers can use correlation ids / context logging. I'll also be using a nuget package "CorrelationId by stevejgordon". We can look at a simple hand rolled approach at the end.
Getting Started
var builder = WebApplication.CreateSlimBuilder(args);
// Setup serilog
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] ({CorrelationId}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
builder.Host.UseSerilog();
// Add Correlation ID services
builder.Services.AddDefaultCorrelationId(options =>
{
options.AddToLoggingScope = true; // Adds CorrelationId to log context
options.RequestHeader = "X-Correlation-ID"; // Incoming header name
options.ResponseHeader = "X-Correlation-ID"; // Outgoing header name
options.IncludeInResponse = true; // Return it in response
});
...
var app = builder.Build();
app.UseCorrelationId();
...Using the power of other peoples code, this is pretty much it. Now it's as simple as logging information. For that we have a simple endpoint
var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/{id}", (int id, ILogger<Program> logger) =>
{
logger.LogInformation("Fetching todo with ID {Id}", id);
return Results.Ok();
});If we look at the logs we should see something like the following

Each log entry now has a guid and this can be used to track the request. In a simple system like this with one request coming in, it might be hard to see the value, but imagine a api handling hundreds requests a second.
This Id can be passed to other services using http headers or be passed back to a client which can be handed over given to the support team when bugs are raised.
Hand Rolling
For those people who like to implement their own thing rather than use another third party dependency, it really does not take much.
The Middleware
First thing first, we want to create some middleware to check if we've been passed a new CorrelationId, if not we want to create a new one. We also want a class we can DI to access the CorrelationId.
public interface ICorrelationIdAccessor
{
string CorrelationId { get; set; }
}
public class CorrelationIdAccessor : ICorrelationIdAccessor
{
public string CorrelationId { get; set; } = string.Empty;
}
public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder app)
{
return app.Use(async (context, next) =>
{
var accessor = context.RequestServices.GetRequiredService<ICorrelationIdAccessor>();
if (!context.Request.Headers.TryGetValue(correlationIdHeader, out var correlationId) ||
string.IsNullOrWhiteSpace(correlationId))
{
correlationId = Guid.NewGuid().ToString();
}
context.TraceIdentifier = correlationId;
accessor.CorrelationId = correlationId;
context.Response.OnStarting(() =>
{
context.Response.Headers[correlationIdHeader] = correlationId;
return Task.CompletedTask;
});
// Push into Serilog's LogContext for this request
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await next.Invoke();
}
});
}Now we just need to register our middleware and CorrelationAccessor
var builder = WebApplication.CreateBuilder(args);
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] ({CorrelationId}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
builder.Services.AddScoped<ICorrelationIdAccessor, CorrelationIdAccessor>();
...
var app = builder.Build();
app.UseCorrelationId();Now we should be getting logs like the following
