Build status NuGet Samples

Logging to elmah.io from ASP.NET Core

If you are looking to log all uncaught errors from ASP.NET Core, you've come to the right place. For help setting up general .NET Core logging similar to log4net, check out Logging from Microsoft.Extensions.Logging.

To log all warnings and errors from ASP.NET Core, install the following NuGet package:

Install-Package Elmah.Io.AspNetCore
dotnet add package Elmah.Io.AspNetCore
<PackageReference Include="Elmah.Io.AspNetCore" Version="5.*" />
paket add Elmah.Io.AspNetCore

In the Startup.cs file, add a new using statement:

using Elmah.Io.AspNetCore;

Call AddElmahIo in the ConfigureServices-method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddElmahIo(options =>
    {
        options.ApiKey = "API_KEY";
        options.LogId = new Guid("LOG_ID");
    });
    // ...
}

Replace API_KEY with your API key (Where is my API key?) and LOG_ID (Where is my log ID?) with the log Id of the log you want to log to.

Call UseElmahIo in the Configure-method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory fac)
{
    // ...
    app.UseElmahIo();
    // ...
}

Call AddElmahIo in the Program.cs file:

builder.Services.AddElmahIo(options =>
{
    options.ApiKey = "API_KEY";
    options.LogId = new Guid("LOG_ID");
});

Replace API_KEY with your API key (Where is my API key?) and LOG_ID (Where is my log ID?) with the log Id of the log you want to log to.

Call UseElmahIo in the Program.cs file:

app.UseElmahIo();

Make sure to call the UseElmahIo-method after installation of other pieces of middleware handling exceptions and auth (like UseDeveloperExceptionPage, UseExceptionHandler, UseAuthentication, and UseAuthorization), but before any calls to UseEndpoints, UseMvc, MapRazorPages, and similar.

That's it. Every uncaught exception will be logged to elmah.io. For an example of configuring elmah.io with ASP.NET Core minimal APIs, check out this sample.

Configuring API key and log ID in options

If you have different environments (everyone has a least localhost and production), you should consider adding the API key and log ID in your appsettings.json file:

{
  // ...
  "ElmahIo": {
    "ApiKey": "API_KEY",
    "LogId": "LOG_ID"
  }
}

Configuring elmah.io is done by calling the Configure-method before AddElmahIo:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ElmahIoOptions>(Configuration.GetSection("ElmahIo"));
    services.AddElmahIo();
}

Notice that you still need to call AddElmahIo to correctly register middleware dependencies.

Finally, call the UseElmahIo-method (as you would do with config in C# too):

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...
    app.UseElmahIo();
    // ...
}

You can still configure additional options on the ElmahIoOptions object:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ElmahIoOptions>(Configuration.GetSection("ElmahIo"));
    services.Configure<ElmahIoOptions>(options =>
    {
        options.OnMessage = msg =>
        {
            msg.Version = "1.0.0";
        };
    });
    services.AddElmahIo();
}

builder.Services.Configure<ElmahIoOptions>(builder.Configuration.GetSection("ElmahIo"));
builder.Services.AddElmahIo();

Notice that you still need to call AddElmahIo to correctly register middleware dependencies.

Finally, call the UseElmahIo-method (as you would do with config in C# too):

app.UseElmahIo();

You can still configure additional options on the ElmahIoOptions object:

builder.Services.Configure<ElmahIoOptions>(builder.Configuration.GetSection("ElmahIo"));
builder.Services.Configure<ElmahIoOptions>(o =>
{
    o.OnMessage = msg =>
    {
        msg.Version = "1.0.0";
    };
});
builder.Services.AddElmahIo();

Logging exceptions manually

While automatically logging all uncaught exceptions is definitely a nice feature, sometimes you may want to catch exceptions and log them manually. If you just want to log the exception details, without all of the contextual information about the HTTP context (cookies, server variables, etc.), we recommend you to look at our integration for Microsoft.Extensions.Logging. If the context is important for the error, you can utilize the Ship-methods available in Elmah.Io.AspNetCore:

try
{
    var i = 0;
    var result = 42/i;
}
catch (DivideByZeroException e)
{
    e.Ship(HttpContext);
}

When catching an exception (in this example an DivideByZeroException), you call the Ship extension method with the current HTTP context as parameter.

From Elmah.Io.AspNetCore version 3.12.* or newer, you can log manually using the ElmahIoApi class as well:

ElmahIoApi.Log(e, HttpContext);

The Ship-method uses ElmahIoApi underneath why both methods will give the same end result.

See Logging breadcrumbs from ASP.NET Core.

Additional options

Setting application name

If logging to the same log from multiple web apps it is a good idea to set unique application names from each app. This will let you search and filter errors on the elmah.io UI. To set an application name, add the following code to the options:

builder.Services.AddElmahIo(o =>
{
    // ...
    o.Application = "MyApp";
});

The application name can also be configured through appsettings.json:

{
  // ...
  "ElmahIo": {
    // ...
    "Application": "MyApp"
  }
}

Hooks

elmah.io for ASP.NET Core supports a range of actions for hooking into the process of logging messages. Hooks are registered as actions when installing the elmah.io middleware:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.OnMessage = message =>
    {
        message.Version = "42";
    };
    options.OnError = (message, exception) =>
    {
        logger.LogError(1, exception, "Error during log to elmah.io");
    };
});

The actions provide a mechanism for hooking into the log process. The action registered in the OnMessage property is called by elmah.io just before logging a new message to the API. Use this action to decorate/enrich your log messages with additional data, like a version number. The OnError action is called if communication with the elmah.io API failed. If this happens, you should log the message to a local log (through Microsoft.Extensions.Logging, Serilog or similar).

Do not log to elmah.io in your OnError action, since that could cause an infinite loop in your code.

While elmah.io supports ignore rules serverside, you may want to filter out errors without even hitting the elmah.io API. Using the OnFilter function on the options object, filtering is easy:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.OnFilter = message =>
    {
        return message.Type == "System.NullReferenceException";
    };
});

The example above, ignores all messages of type System.NullReferenceException.

Decorate from HTTP context

When implementing the OnMessage action as shown above you don't have access to the current HTTP context. Elmah.Io.AspNetCore already tries to fill in as many fields as possible from the current context, but you may want to tweak something from time to time. In this case, you can implement a custom decorator like this:

public class DecorateElmahIoMessages : IConfigureOptions<ElmahIoOptions>
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public DecorateElmahIoMessages(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Configure(ElmahIoOptions options)
    {
        options.OnMessage = msg =>
        {
            var context = httpContextAccessor.HttpContext;
            msg.User = context?.User?.Identity?.Name;
        };
    }
}

Then register IHttpContextAccessor and the new class in the ConfigureServices method in the Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    services.AddSingleton<IConfigureOptions<ElmahIoOptions>, DecorateElmahIoMessages>();
    // ...
}

Then register IHttpContextAccessor and the new class in the in the Program.cs file:

builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IConfigureOptions<ElmahIoOptions>, DecorateElmahIoMessages>();

Decorating messages using IConfigureOptions requires Elmah.Io.AspNetCore version 4.1.37 or newer.

Include source code

You can use the OnMessage action to include source code to log messages. This will require a stack trace in the Detail property with filenames and line numbers in it.

There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode NuGet package and call the WithSourceCodeFromPdb method in the OnMessage action:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.OnMessage = msg =>
    {
        msg.WithSourceCodeFromPdb();
    };
});

Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.

Including source code on log messages is available in the Elmah.Io.Client v4 package and forward.

Remove sensitive form data

The OnMessage event can be used to filter sensitive form data as well. In the following example, we remove the server variable named Secret-Key from all messages, before sending them to elmah.io.

builder.Services.AddElmahIo(options =>
{
    // ...
    options.OnMessage = msg =>
    {
        var item = msg.ServerVariables.FirstOrDefault(x => x.Key == "Secret-Key"); 
        if (item != null)
        {
            msg.ServerVariables.Remove(item);
        }
    };
});

Formatting exceptions

A default exception formatter is used to format any exceptions, before sending them off to the elmah.io API. To override the format of the details field in elmah.io, set a new IExceptionFormatter in the ExceptionFormatter property on the ElmahIoOptions object:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.ExceptionFormatter = new DefaultExceptionFormatter();
}

Besides the default exception formatted (DefaultExceptionFormatter), Elmah.Io.AspNetCore comes with a formatter called YellowScreenOfDeathExceptionFormatter. This formatter, outputs an exception and its inner exceptions as a list of exceptions, much like on the ASP.NET yellow screen of death. If you want, implementing your own exception formatter, requires you to implement a single method.

Logging responses not throwing an exception

As default, uncaught exceptions (500's) and 404's are logged automatically. Let's say you have a controller returning a Bad Request and want to log that as well. Since returning a 400 from a controller doesn't trigger an exception, you will need to configure this status code:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.HandledStatusCodesToLog = new List<int> { 400 };
}

The list can also be configured through appsettings.json:

{
  // ...
  "ElmahIo": {
    // ...
    "HandledStatusCodesToLog": [ 400 ],
  }
}

When configuring status codes through the appsettings.json file, 404s will always be logged. To avoid this, configure the list in C# as shown above.

Logging through a proxy

Since ASP.NET Core no longer support proxy configuration through web.config, you can log to elmah.io by configuring a proxy manually:

builder.Services.AddElmahIo(options =>
{
    // ...
    options.WebProxy = new System.Net.WebProxy("localhost", 8888);
}

In this example, the elmah.io client routes all traffic through http://localhost:8000.

ASP.NET Core 8

The Elmah.Io.AspNetCore package can be installed exactly how it is described above in ASP.NET Core 8. We still recommend doing that, so if you don't experience any problems with this approach, there's no need to read this section (unless you are just curious).

ASP.NET Core 8 introduces a new way of logging and handling exceptions: IExceptionHandler. You have probably already seen a line similar to this in the Program.cs file:

app.UseExceptionHandler("/Error");

This installs the exception-handling middleware bundled with ASP.NET Core. The new feature provides you with the possibility of registering custom exception handlers run as part of the built-in middleware. This is done by registering one or more classes that implement the IExceptionHandler interface. The Elmah.Io.AspNetCore package bundles such a class from version 5.1 and forward. To install it, include the following code in the Program.cs file:

builder.Services.AddElmahIo(options =>
{
    options.ApiKey = "API_KEY";
    options.LogId = new Guid("LOG_ID");
});
builder.Services.AddExceptionHandler<ElmahIoExceptionHandler>();

The AddElmahIo method will set up the dependencies as usual and the AddExceptionHandler method will register an exception handler logging exceptions to elmah.io. You still need to call the UseExceptionHandler method with either a path or empty options:

app.UseExceptionHandler("/Error");

// or

app.UseExceptionHandler(_ => {});

Calling the overload of UseExceptionHandler without any parameters will not work here.

As already mentioned, we only recommend using this code if the normal approach doesn't work. We may switch over to using this approach later on but the code is currently experimental. One advantage of using the exception handler over the elmah.io middleware is to avoid the scenario where you want to use both the elmah.io and exception handler middleware. As an example, this code will not work:

app.UseElmahIo();
app.UseExceptionHandler("/Error");

This is because the exception handle "swallows" all exceptions before the elmah.io middleware is notified. Registering the ElmahIoExceptionHandler class and not calling UseElmahIo will fix this problem.

Logging health check results

Check out Logging heartbeats from ASP.NET Core for details.


This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website