.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.

  1. Send an ID between API calls and have it included in the log files.
  2. The ID should be in the same property in all logs.
  3. Easily access the correlation ID.
  4. 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.

Onward, to the 4th and final post. Automatically passing that correlation id, to my backend services through my clients.