Wouldn’t it be nice if every time the user faced an error they could provide you with a ID that helps you dig into heaps of logs to spot exactly what went wrong. This is really helpful when your application is relatively new. Let’s see how to wire this up.

I will be using Autofac as my dependency injection framework of choice but you can use any DI that basically supports lifetimes per request. Also, we will be using the nuget package Microsoft.Diagnostics.Correlation. The nuget package is not a hard requirement and we could have very well used our own correlation Id and request Id creation logic.

The Interface

First we define  our interface. We would need a way to get the correlation ID which is a single ID that spans a session and a request ID that is generated for each request.

    public interface IGlobalStore
    {
        string GetCorrelationId();
        string GetRequestId();
    }

The Class

Now, it’s time to implement the interface. Note that we aren’t creating setter’s here and the only way to set a new value is through the constructor. This way when our DI is setup we safeguard ourselves from accidentally setting new values from within the business logic which is easy to do if a new developer walks into the team. Of course you can new up the object but we are assuming developers are sane enough and know that a DI framework exists :).

    public class GlobalStore : IGlobalStore
    {
        private string CorrelationId { get; set; }
        private string RequestId { get; set; }

        public GlobalStore() { }

        public GlobalStore(CorrelationContext correlation_context)
        {
            if (correlation_context != null)
            {
                CorrelationId = correlation_context.CorrelationId;
                RequestId = correlation_context.RequestId;
            }
        }

        public string GetCorrelationId()
        {
            return CorrelationId;
        }

        public string GetRequestId()
        {
            return RequestId;
        }
    }

Setting Up The DI

Now we need to create a new instance of GlobalStore for every HTTP request. Since we are using a dependency injection framework setting this up is pretty easy.

var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
//This provides HTTPContext
builder.RegisterModule(new AutofacWebTypesModule());

//..
//Other registrations
//..

builder.RegisterType<GlobalStore>()
                .WithParameter(new TypedParameter(typeof(CorrelationContext), new CorrelationContextFactory().CreateContext(HttpContext.Current.Request)))
                .As<IGlobalStore>().InstancePerRequest();

//..
//Other registrations
//..

IContainer container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

All that is left now is to use this in your code by injecting IGlobalStore in your log service and using the getter’s to add the IDs in your database.

The Final Piece : Passing It To The Client

The last piece of code that you need is a way for clients to report the correlation ID back to the developers so that you can pull up the relevant logs. For this we create a ExceptionFilterAttributeThanks to Omkar Khair for writing the below code

    public class ApiExceptionFilter : ExceptionFilterAttribute
    {
        private IGlobalStore _gStore { get; set; }

        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            var scope = actionExecutedContext.Request.GetDependencyScope();
            var _gStoreFactory = scope.GetService(typeof(IGlobalStore)) as IGlobalStore;
            string correlationId = "";
            //Good to check if _gStore is null to avoid null exception.
            if (_gStore != null)
            {
                correlationId = _gStore.GetCorrelationId();
            }
            string exceptionMessage = string.Empty;
            if (actionExecutedContext.Exception.InnerException == null)
            {
                exceptionMessage = actionExecutedContext.Exception.Message;
            }
            else
            {
                exceptionMessage = actionExecutedContext.Exception.InnerException.Message;
            }

            var response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent($"{{\"correlationId\": \"{correlationId}\", \"message\": \"{exceptionMessage}. Correlation ID: {correlationId}\"}}"),
                ReasonPhrase = "An unexpected error occured. Please contact support if problem persists. Correlation ID: " + correlationId
            };
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            actionExecutedContext.Response = response;
        }
    }