.NET Core Log Correlation - Easy Access to the Correlation Id
Easily accessing the correlation Id that I am passing around, and, in my frontend site, create that correlation id
My 3rd requirement is easily accessing the correlation Id that I am passing around, and, in my frontend site, create that correlation id. I am passing around the header X-SessionId
that is being stored in log entries as SessionId
.
This is part 3 of my blog post series on log correlation between a frontend website and a backend service and covers easy access to the correlation id. With that, it also covers creating a correlation ID in the frontend site.
- Send an ID between API calls and have it included in the log files.
- The ID should be in the same property in all logs.
- Easily access the correlation ID.
- When possible, it should automatically be added to the correct
HttpClient
requests.
To make the correlation data easy to access, in the middleware that we just created in part 2, we can have it put the correlation header value in the temporary data store. This data is stored in the context and only available for the current request. In the front-end site, we will generate the id instead of getting it from the header.
Use this LogHeaderMiddleware
class instead of the one from the previous step. The difference is it sets the X-SessionId
object in the context to headers[0]
if it existed. If it doesn't it set it Guid.NewGuid().ToString()
so we can have an id further down the line.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Vecc.LogCorrelation.Example.Source.Services.Middleware
{
public class LogHeaderMiddleware
{
private readonly RequestDelegate _next;
public LogHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var header = context.Request.Headers["X-SessionId"];
string sessionId;
if (header.Count > 0)
{
sessionId = header[0];
}
else
{
sessionId = Guid.NewGuid().ToString();
}
context.Items["X-SessionId"] = sessionId;
var logger = context.RequestServices.GetRequiredService<ILogger<LogHeaderMiddleware>>();
using (logger.BeginScope("{@SessionId}", sessionId))
{
await this._next(context);
}
}
}
}
Note: I do not recommend that you use the header when doing this in the frontend site. We'll go over that in the next post. Just 2 quick reasons why, a SQL injection attack through a side channel. Or a DDOS if that header gets passed around your network by sending in a few hundred megs of data in that header, etc. It could be an exploitable attack vector that may be a bit tricky to diagnose and figure out. Attackers are smart.
Now that we have the context items populated, we can create an interface and implementing class. We can then use the IHttpContextAccessor
object to make accessing the contents of that header easy without needing to pass around the HttpContext
and manually getting that value every time.
First, create your interface:
namespace Vecc.LogCorrelation.Example.Target.Services
{
public interface ISessionIdAccessor
{
string GetSessionId();
}
}
Then create your class:
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Vecc.LogCorrelation.Example.Target.Services.Internal
{
public class DefaultSessionIdAccessor : ISessionIdAccessor
{
private readonly ILogger<DefaultSessionIdAccessor> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public DefaultSessionIdAccessor(ILogger<DefaultSessionIdAccessor> logger, IHttpContextAccessor httpContextAccessor)
{
this._logger = logger;
this._httpContextAccessor = httpContextAccessor;
}
public string GetSessionId()
{
try
{
var context = this._httpContextAccessor.HttpContext;
var result = context?.Items["X-SessionId"] as string;
return result;
}
catch (Exception exception)
{
this._logger.LogWarning(exception, "Unable to get original session id header");
}
return string.Empty;
}
}
}
Now register the service and the HttpContextAccessor
if it isn't already by adding this to the ConfigureServices
method of your Startup
class:
services.AddHttpContextAccessor();
services.AddScoped<IOriginalSessionIdAccessor, DefaultOriginalSessionIdAccessor>();
You can now access the X-SessionId
very easily and consistently, it also makes testing classes that need that value a whole lot easier by not needing to fake out the HttpContext
and headers for every test.
My 3rd requirement is now satisfied, easily access the correlation id throughout my code.