Simple ELMAH-like web error logging for ASP.NET Core using MongoDB

ELMAH-like web error logging for ASP.NET Core
While making this blog I needed a quick way to log failed requests for later analysis. If you've developed ASP.NET applications in the past, then you've probably used good old ELMAH package, which automagically logs your unsuccessful requests. Good news, this is easily achievable in ASP.NET Core via something called middleware. If you're not familiar with middleware then I suggest you go and take a few minutes with the article.

Basically, you use the configure method in Startup class to configure the request pipeline. There you can plant your logging functionality. Pay close attention to the ordering of the pipeline steps: your logging step should be the first one or else it might be intercepted by another step.


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        try
        {
            await next();
        }
        finally
        {
            int code = context.Response.StatusCode;

            if (code > 400)
            {
                IMongoDatabase mongo = context.RequestServices.GetService();
                Exception error = context.Features.Get()?.Error;

                IMongoCollection collection = mongo.GetCollection(typeof(RequestLogEntry).Name);

                await collection.InsertOneAsync(new RequestLogEntry
                {
                    Code = code,
                    Exception = error?.Message,
                    StackTrace = error?.StackTrace,
                    Path = context.Request.Path,
                    Referer = context.Request.Headers["Referer"],
                    Query = context.Request.QueryString.ToString(),
                    UserAgent = context.Request.Headers["user-agent"],
                    Method = context.Request.Method,
                    RemoteIp = context.Connection.RemoteIpAddress?.ToString()
                });
            }
        }
    });

    //other steps...
}

In the preceding example, I'm trying to run rest of the pipeline and finally checking the response code. If it's anything above 400, I'm going to log it into mongo. Latter is set up as a singleton in the ConfigureServices method as shown in the following snippet:


public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton(provider =>
    {
        MongoDefaults.GuidRepresentation = GuidRepresentation.Standard;
        MongoClient client = new MongoClient("mongodb://127.0.0.1:21210");
        IMongoDatabase db = client.GetDatabase("blog");
        bool ping = db.RunCommandAsync((Command) "{ping:1}").Wait(1000);

        if (!ping)
            throw new Exception("Couldn't connect to mongo");

        return db;
    });
    ...
}

    

Note that I'm trying to run a ping command to check immediately if Mongo is responding or else it would fail during runtime. After you've collected some data, you can either query it via Robomongo (now Robo 3T) or build a simple UI on top of it:

ELMAH-like web error logging for ASP.NET Core dashboard