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 (likeUseDeveloperExceptionPage
,UseExceptionHandler
,UseAuthentication
, andUseAuthorization
), but before any calls toUseEndpoints
,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.
Breadcrumbs
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
requiresElmah.Io.AspNetCore
version4.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, 404
s 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