Use CorrelationId to track calls in ASP.NET 4
Correlation is not causation
But it will help you find the cause. Correlation Id](https://www.rapid7.com/blog/post/2016/12/23/the-value-of-correlation-ids/) is a value that can for instance be used to track calls in log files as it moves between services. It's a common practice when architecting a system as Microservices, but it's a useful practice in any system that involves calls between services and APIs, since it allows an fairly easy way to group log messages from different logs. It also helps when dealing with systems that span time zones and organizations since the correlation id can be easily passed along in support messages.
Now that sounds so good I gotta get me some of that.
For ASP.NET Core it's easy peasy using the Correlation Id library, but for old ASP.NET 4 I haven't found an out of the box solution so I decided to roll my own. The goal is for all log calls to display the correlation id, and to add correlation id to the headers of any calls using HttpClient.
Imitation is the sincerest form of flattery
Writing a custom layout renderer for NLog took some time for reasons I'll get into in a later post (promises, promises), but I got it in place and it works. I took inspiration from the Correlation Id library and use a ICorrelationIdAccessor
to be able to inject the correlation id into service classes.
If you look at the CorrelationId library you will see the ICorrelationContextAccessor
. It uses a CorrelationContext that includes the CorrelationId and http header value used to pass the CorrelationId but I know I'm using x-correlation-id
so I've simplified it to just be a string.
public interface ICorrelationIdAccessor
{
string CorrelationId { get; }
}
public class CorrelationIdAccessor : ICorrelationIdAccessor
{
private static AsyncLocal<string> _correlationId = new AsyncLocal<string>();
public string CorrelationId
{
get => _correlationId .Value;
set => _correlationId .Value = value;
}
}
The above will let you set the CorrelationId at the beginning of a call to a Web API or any other ASP.NET call and be able to access it from anywhere during that call. So why don't we do just that.
ASP.NET Web API 2
If you're using ASP.NET Web API 2 you can easily create a middleware and set the CorrelationId that way. This will check if the header has a correlationId set and if not generate a new one so we can still enjoy that sweet correlation in our log files.
var correlationId = request.Headers.Get("x-correlation-id");
// If the correlationId is not set on the header, generate a new GUID and use that instead.
if (string.IsNullOrWhatespace(correlationId))
correlationId = Guid.NewGuid().ToString();
After that it grabs the CorrelationIdAccessor and sets the CorrelationId.
var scope = request.GetDependencyScope();
var accessor = scope.GetService<CorrelationContextAccessor>();
accessor.CorrelationId = correlationId;
The full handler looks like
public class CorrelationIdHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken)
{
// See if there's a CorrelationId on the request header
// I actually store the header name in a const string, but for brevity it's a magic string here
var correlationId = request.Headers.Get("x-correlation-id");
// If the correlationId is not set on the header, generate a new GUID and use that instead.
if (string.IsNullOrWhatespace(correlationId))
correlationId = Guid.NewGuid().ToString();
// You can do var accessor = new CorrelationIdAccessor();
// but since I've got Dependency Injection setup I can grab it from IDependencyScope
var scope = request.GetDependencyScope();
var accessor = scope.GetService<CorrelationIdAccessor>();
accessor.CorrelationId = correlationId;
}
}
ASP.NET WCF and friends
But what about if you're not using Web API 2? Well you can always go straight to Global.asax.cs and Application_BeginRequest
public class Global : System.Web.HttpApplication
{
protected void Application_BeginRequest(object sender, EventArgs e)
{
// See if there's a CorrelationId on the request header. Here I'll use HttpContext.Current to get the request header
var correlationId = HttpContext.Current.Request.Headers.Get("x-correlation-id");
// If the correlationId is not set on the header, generate a new GUID and use that instead.
if (string.IsNullOrWhatespace(correlationId))
correlationId = Guid.NewGuid().ToString();
// If you're using a Dependency Container framework you can probably get the CorrelationIdAccessor that way
// If not you can still use new
var accessor = new CorrelationIdAccessor();
accessor.CorrelationId = correlationId;
}
}
It's pretty similar to the middleware implementation, but here I use new on the ContextIdAccessor since I actually haven't test if I can use GetDependencyScope() in this instance... :)
It's correlation time
After that you can either new up a CorrelationIdAccessor or inject a ICorrelationIdAccessor into your classes and for that specific call you will get the same CorrelationId everywhere.
What sorcery is that?!?
Now if you looked at the CorrelationIdAccessor, or in my case looked at the source code for the CorrelationContextAccessor, and thought "what's that voodoo happening using AsyncLocal
?!? Well I'll get into that in my next post. Hopefully it won't take another two years for me to get around to that... :)
I am a developer, a AIK Hockey fan and an avid TV-viewer and this is my blog.
No feedback yet
Form is loading...