Correlate provides flexible .NET Core support for correlation ID in ASP.NET Core and HttpClient.
Install Correlate via the Nuget package manager or dotnet
cli.
dotnet add package Correlate
For ASP.NET Core integration:
dotnet add package Correlate.AspNetCore
In an ASP.NET Core (MVC) application, register Correlate to handle incoming requests with a correlation id. Correlate will create a request scoped async context holding the correlation id, which can then propagate down the request pipeline.
When using HttpClient
to call other services, you can use HttpClientFactory
to attach a delegating handler to any HttpClient
which will propagate the correlation id header to the outgoing request for cross service correlation. Further more, there are other integration packages that also propagate the correlation id to other transports (see down below).
Configure your application:
using Correlate.AspNetCore;
using Correlate.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register services.
services.AddCorrelate(options =>
options.RequestHeaders = new []
{
// List of incoming headers possible. First that is set on given request is used and also returned in the response.
"X-Correlation-ID",
"My-Correlation-ID"
}
);
// Register a typed client that will include the correlation id in outgoing request.
services
.AddHttpClient<IMyService, MyService>()
.CorrelateRequests("X-Correlation-ID");
services.AddMvcCore();
}
public void Configure(IApplicationBuilder app)
{
// Registers a diagnostics request observer.
app.UseCorrelate();
app.UseMvc();
}
}
Using this setup, for any incoming request that contains a X-Correlation-ID
or My-Correlation-ID
header, the correlation id from that header will be used throughout the request pipeline. For any incoming request without either header, a unique correlation id is generated instead.
Secondly, all responses will receive a matching response header.
Thirdly, for all outbound HTTP calls that are sent via the HttpClient
provided to MyService
instances, a X-Correlation-ID
request header is added.
Before a request flows down the pipeline, a log scope is created with a CorrelationId
property, containing the correlation id. As such, every log event that is logged during the entire request context will be enriched with the CorrelationId
property.
Most popular log providers will be able to log the correlation id with minimal set up required.
Here's some providers that require no set up or custom code, only configuration:
- Serilog:
new LoggerConfiguration().Enrich.FromLogContext()
https://github.com/serilog/serilog/wiki/Enrichment#the-logcontext - NLog: https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer
${mdlc:item=CorrelationId}
To access the correlation id anywhere in code, inject an instance of ICorrelationContextAccessor
in your constructor.
public class MyService
{
public MyService(ICorrelationContextAccessor correlationContextAccessor)
{
string correlationId = correlationContextAccessor.CorrelationContext.CorrelationId;
}
}
Note:
correlationContextAccessor.CorrelationContext
can be null, whenMyService
is not scoped to a request. Thus, when used outside of ASP.NET (not using the middleware component), you should create the context usingI(Async)CorrelationManager
orIActivityFactory
respectively (depending on the use case) for each unique subprocess.
To simplify managing correlation contexts, the I(Async)CorrelationManager
can be used. It takes care of the logic to create the context properly. This is especially useful when running background tasks, console apps, Windows services, etc. which need to interact with external services. Think of message broker handlers, scheduled task runners, etc.
public class MyWorker
{
private readonly IAsyncCorrelationManager _correlationManager;
private readonly MyService _myService;
public MyWorker(IAsyncCorrelationManager correlationManager, MyService myService)
{
_correlationManager = correlationManager;
_myService = myService;
}
public async Task RunAsync()
{
while (true)
{
await _correlationManager.CorrelateAsync(async () => {
// Do work in a scoped correlation context with its own correlation id.
await myService.MakeHttpCallAsync();
});
await Task.Delay(5000);
}
}
}
In this example the MakeHttpCallAsync()
call is executed in a correlation context, for which a correlation id is automatically generated and attached to the outgoing request provided the HttpClient
is set up with the delegating handler:
services
.AddHttpClient<IMyService, MyService>()
.CorrelateRequests("X-Correlation-ID");
As an example, consider a use case where the order id should be used as a correlation id. The CorrelationManager
has an overload that accepts a custom correlation id:
await _correlationManager.CorrelateAsync(orderId, () => {
// Do work
});
CorrelationManager
provides both a synchronous and asynchronous implementation, and they can be requested from the service provider independently:
ICorrelationManager
IAsyncCorrelationManager
Note that the Correlate internals are intrinsically asynchronous as it relies on
AsyncLocal<T>
to save the correlation context. The synchronous implementation is useful for integrations that have a synchronous API surface but are still used in asynchronous context, but should only be used as such.
By default, when generating a correlation id, the GuidCorrelationIdFactory
will produce guid based correlation ids.
As an alternative, there's also a RequestIdentifierCorrelationIdFactory
which produces base32 encoded correlation ids. To use this or a custom implementation instead, simply register it manually in the service container.
services.AddSingleton<ICorrelationIdFactory, RequestIdentifierCorrelationIdFactory>();
Framework/library | Type | Package | Description |
---|---|---|---|
Rebus | Service bus | Rebus.Correlate | Rebus integration of Correlate to correlate message flow via any supported Rebus transport. |
Hangfire | Job scheduler | Hangfire.Correlate | Hangfire integration of Correlate to add correlation id support to Hangfire background/scheduled jobs. |
Please consider that since .NET Core 3.1 and up there is built-in support for W3C TraceContext (blog) and that there are other distributed tracing libraries with more functionality than Correlate. Personally, I am using System.Diagnostics.ActivitySource
and OpenTelemetry in my professional work.
- .NET 9.0, .NET 8.0
- .NET Standard 2.0
- ASP.NET Core 9.0/8.0
- Visual Studio 2022
- .NET 9 SDK
- .NET 8 SDK
- .NET 3.1 SDK
PR's are welcome. Please rebase before submitting, provide test coverage, and ensure the AppVeyor build passes. I will not consider PR's otherwise.
- skwas (author/maintainer)