End-to-End Tracing with Correlation IDs in ASP.NET Core
Learn how to implement Correlation IDs in ASP.NET Core APIs to trace requests across services, improve observability, and simplify debugging. This guide covers middleware, HttpClient integration, and
🧩 What Is a Correlation ID and How to Use It in .NET Core APIs
In modern microservices architectures, tracing a single request across multiple services can be a nightmare without proper instrumentation.
This is where Correlation IDs come to the rescue.
🧩 A Correlation ID is a unique identifier assigned to every request. It travels across services—linking logs, tracing execution paths, and dramatically improving observability.
Let’s break down the why, how, and best practices of Correlation IDs in .NET applications.
🧠 What is a Correlation ID?
A Correlation ID is a unique identifier assigned to a request entering your system. It travels across all services handling that request, enabling you to trace its entire journey—ideal for:
Debugging distributed systems
Monitoring latency and failures
Improving log searchability
✨ Benefits of Using Correlation IDs
End-to-end traceability across microservices
Faster debugging and diagnostics by filtering logs per request
Improved observability with tooling support like Serilog, OpenTelemetry, etc.
💡 Why It Matters
Implementing Correlation IDs provides significant value for both developers and operations teams:
✅ End-to-End Traceability: Track a request as it flows through various services.
✅ Faster Debugging: Quickly pinpoint where things went wrong.
✅ Cleaner Log Filtering: Easily isolate logs tied to a specific request or session.
✅ Integration with Modern Tools: Works seamlessly with structured logging tools like Serilog, Seq, and observability tools like OpenTelemetry.
🚀 Real-World Use Cases
Here’s where Correlation IDs become indispensable in production environments:
1. API Gateway → Downstream Services
Ensure each microservice logs the same Correlation ID for a user request—critical for traceability.
2. Retry Logic or Circuit Breakers
Know whether retries are part of the same user request to avoid false positives during error handling.
3. Service-to-Service Communication
Even internal APIs benefit from correlation during failures, timeouts, or performance spikes.
4. Queue-Based Systems
Attach Correlation IDs to message headers to preserve context in asynchronous processing.
✅ Best Practices for Using Correlation IDs in .NET
To implement this successfully, follow these practices:
Use a Consistent Header Name
Standardize on something likeX-Correlation-ID
.Generate the ID at the Entry Point
Do this in API Gateway or Middleware. If a client doesn’t send one, create a new unique ID.Log It at Every Level
From controller to service to repository—log it everywhere.Include It in All Outgoing Requests
Pass it along in HTTP calls, message queues, and other communication layers.Visualize in Dashboards
Add it to your monitoring tools for easier filtering and tracing.Fallback When Missing
Never assume the client sends the ID. Always generate one if it's missing.
🛠️ Implementation in ASP.NET Core API
Step 1: Define a Correlation ID Accessor
We need a central place to store the current request’s correlation ID.
public interface ICorrelationIdAccessor
{
string CorrelationId { get; set; }
}
public class CorrelationIdAccessor : ICorrelationIdAccessor
{
public string CorrelationId { get; set; } = Guid.NewGuid().ToString();
}
Step 2: Create Middleware to Read or Generate Correlation ID
This middleware checks incoming requests for an existing correlation ID header (e.g., X-Correlation-ID
). If not present, it creates one.
public class CorrelationIdMiddleware
{
private const string CorrelationIdHeader = "X-Correlation-ID";
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ICorrelationIdAccessor accessor)
{
var correlationId = context.Request.Headers.TryGetValue(CorrelationIdHeader, out var cid)
? cid.ToString()
: Guid.NewGuid().ToString();
accessor.CorrelationId = correlationId;
context.Response.Headers[CorrelationIdHeader] = correlationId;
await _next(context);
}
}
Step 3: Add DelegatingHandler for Outgoing Requests
To ensure every HttpClient
request also includes the correlation ID, use a message handler.
public class CorrelationIdHandler : DelegatingHandler
{
private readonly ICorrelationIdAccessor _accessor;
public CorrelationIdHandler(ICorrelationIdAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(_accessor.CorrelationId))
{
request.Headers.TryAddWithoutValidation("X-Correlation-ID", _accessor.CorrelationId);
}
return await base.SendAsync(request, cancellationToken);
}
}
Step 4: Register Everything in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Register dependencies
builder.Services.AddSingleton<ICorrelationIdAccessor, CorrelationIdAccessor>();
builder.Services.AddTransient<CorrelationIdHandler>();
builder.Services.AddScoped<MyService>();
// Register named HttpClient with the handler
builder.Services.AddHttpClient("MyApiClient")
.AddHttpMessageHandler<CorrelationIdHandler>();
var app = builder.Build();
// Register middleware
app.UseMiddleware<CorrelationIdMiddleware>();
app.MapControllers();
app.Run();
Step 5: Create the Service and Controller
public class MyService
{
private readonly HttpClient _httpClient;
public MyService(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("MyApiClient");
}
public async Task<string> CallExternalApiAsync()
{
var response = await _httpClient.GetAsync("https://httpbin.org/headers");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
[ApiController]
[Route("api/[controller]")]
public class ExternalApiController : ControllerBase
{
private readonly MyService _myService;
public ExternalApiController(MyService myService)
{
_myService = myService;
}
[HttpGet("call")]
public async Task<IActionResult> Call()
{
var result = await _myService.CallExternalApiAsync();
return Ok(result);
}
}
📄 Result from Swagger UI
✅ Best Practices
✅ Standardize the header name (e.g., X-Correlation-ID)
✅ Generate the ID as early as possible
✅ Log the ID in every service and every layer
✅ Ensure the ID is passed in all outgoing requests and responses
✅ Use tracing tools like OpenTelemetry or Jaeger for advanced scenarios
🧠 Summary
Correlation IDs are a lightweight but powerful tool to enhance traceability and observability in distributed systems. With minimal middleware and handler setup, .NET Core APIs can fully support this feature — making your logs and metrics instantly more useful.