Logging to elmah.io from Serilog
Serilog is a great addition to the flowering .NET logging community, described as “A no-nonsense logging library for the NoSQL era” on their project page. Serilog works just like other logging frameworks such as log4net and NLog but offers a great fluent API and the concept of sinks (a bit like appenders in log4net). Sinks are superior to appenders because they threat errors as objects rather than strings, a perfect fit for elmah.io which itself is built on NoSQL. Serilog already comes with native support for elmah.io, which makes it easy to integrate with any application using Serilog.
Adding this type of logging to your windows and console applications is just as easy. Add the Serilog.Sinks.ElmahIo
NuGet package to your project:
Install-Package Serilog.Sinks.ElmahIo
dotnet add package Serilog.Sinks.ElmahIo
<PackageReference Include="Serilog.Sinks.ElmahIo" Version="5.*" />
paket add Serilog.Sinks.ElmahIo
To configure Serilog, add the following code to the Application_Start method in global.asax.cs:
var log =
new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
.CreateLogger();
Log.Logger = log;
Replace API_KEY
with your API key (Where is my API key?) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID?).
First, we create a new LoggerConfiguration and tell it to write to elmah.io. The log object can be used to log errors and you should register this in your IoC container. In this case, we don't use IoC, that is why the log object is set as the public static Logger property, which makes it accessible from everywhere.
To log exceptions to elmah.io through Serilog use the Log
class provided by Serilog:
try
{
// Do some stuff that may cause an exception
}
catch (Exception e)
{
Log.Error(e, "The actual error message");
}
The Error method tells Serilog to log the error in the configured sinks, which in our case logs to elmah.io. Simple and beautiful.
Note
Always call Log.CloseAndFlush();
before your program terminates.
Logging custom properties
Serilog supports logging custom properties in three ways: As part of the log message, through enrichers, and using LogContext
. All three types of properties are implemented in the elmah.io sink as part of the Data dictionary to elmah.io.
The following example shows how to log all three types of properties:
var logger =
new LoggerConfiguration()
.Enrich.WithProperty("ApplicationIdentifier", "MyCoolApp")
.Enrich.FromLogContext()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
.CreateLogger();
using (LogContext.PushProperty("ThreadId", Thread.CurrentThread.ManagedThreadId))
{
logger.Error("This is a message with {Type} properties", "custom");
}
Beneath the Data tab on the logged message details, the ApplicationIdentifier
, ThreadId
, and Type
properties can be found.
Serilog.Sinks.ElmahIo
provides a range of reserved property names, that can be used to fill in data in the correct fields on the elmah.io UI. Let's say you want to fill the User field using structured logging only:
logger.Information("{Quote} from {User}", "Hasta la vista, baby", "Arnold Schwarzenegger");
This will fill in the value Arnold Schwarzenegger
in the User
field, as well as add two key/value pairs (Quote and User) to the Data tab on elmah.io. For a reference of all possible property names, check out the property names on CreateMessage.
Message hooks
Serilog.Sinks.ElmahIo
provides message hooks similar to the integrations with ASP.NET and ASP.NET Core.
Note
Message hooks require Serilog.Sinks.ElmahIo
version 3.3.0
or newer.
Decorating log messages
To include additional information on log messages, you can use the OnMessage event when initializing the elmah.io target:
Log.Logger =
new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
{
OnMessage = msg =>
{
msg.Version = "1.0.0";
}
})
.CreateLogger();
The example above includes a version number on all errors. Since the elmah.io sink also picks up enrichers specified with Serilog, this example could be implemented by enriching all log messages with a field named version
.
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:
Log.Logger =
new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
{
OnMessage = msg =>
{
msg.WithSourceCodeFromPdb();
}
})
.CreateLogger();
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
Note
Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
Handle errors
To handle any errors happening while processing a log message, you can use the OnError event when initializing the elmah.io sink:
Log.Logger =
new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
{
OnError = (msg, ex) =>
{
Console.Error.WriteLine(ex.Message);
}
})
.CreateLogger();
The example implements a callback if logging to elmah.io fails. How you choose to implement this is entirely up to your application and tech stack.
Error filtering
To ignore specific messages based on their content, you can use the OnFilter event when initializing the elmah.io sink:
Log.Logger =
new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
{
OnFilter = msg =>
{
return msg.Title.Contains("trace");
}
})
.CreateLogger();
The example above ignores any log message with the word trace
in the title.
ASP.NET Core
Serilog provides a package for ASP.NET Core, that routes log messages from inside the framework through Serilog. We recommend using this package together with the elmah.io sink, in order to capture warnings and errors happening inside ASP.NET Core.
To use this, install the following packages:
Install-Package Serilog.AspNetCore
Install-Package Serilog.Sinks.ElmahIo
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.ElmahIo
<PackageReference Include="Serilog.AspNetCore" Version="6.*" />
<PackageReference Include="Serilog.Sinks.ElmahIo" Version="5.*" />
paket add Serilog.AspNetCore
paket add Serilog.Sinks.ElmahIo
Configure Serilog using the UseSerilog
method in the Program.cs
file:
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
{
MinimumLogEventLevel = LogEventLevel.Information,
}));
}
Now, all warnings, errors, and fatals happening inside ASP.NET Core are logged to elmah.io.
A common request is to include all of the HTTP contextual information you usually get logged when using a package like Elmah.Io.AspNetCore
. We have developed a specialized NuGet package to include cookies, server variables, etc. when logging through Serilog from ASP.NET Core. To set it up, install the Elmah.Io.AspNetCore.Serilog
NuGet package:
Install-Package Elmah.Io.AspNetCore.Serilog
dotnet add package Elmah.Io.AspNetCore.Serilog
<PackageReference Include="Elmah.Io.AspNetCore.Serilog" Version="5.*" />
paket add Elmah.Io.AspNetCore.Serilog
Then, call the UseElmahIoSerilog
method in Program.cs
file:
// ... Exception handling middleware
app.UseElmahIoSerilog();
// ... UseMvc etc.
The middleware uses Serilog's LogContext
feature to enrich each log message with additional properties. To turn on the log context, extend your Serilog config:
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.ElmahIo(/*...*/)
.Enrich.FromLogContext() // <-- add this line
);
There's a problem with this approach when an endpoint throws an uncaught exception. Microsoft.Extensions.Logging logs all uncaught exceptions as errors, but the LogContext
is already popped when doing so. The recommended approach is to ignore these errors in the elmah.io sink and install the Elmah.Io.AspNetCore
package to log uncaught errors to elmah.io (as explained in Logging from ASP.NET Core). The specific error message can be ignored in the sink by providing the following filter during initialization of Serilog:
.WriteTo.ElmahIo(/*...*/)
{
// ...
OnFilter = msg =>
{
return
msg != null
&& msg.TitleTemplate != null
&& msg.TitleTemplate.Equals(
"An unhandled exception has occurred while executing the request.",
StringComparison.InvariantCultureIgnoreCase);
}
})
ASP.NET
Messages logged through Serilog in an ASP.NET WebForms, MVC, or Web API application can be enriched with a range of HTTP contextual information using the SerilogWeb.Classic
NuGet package. Start by installing the package:
Install-Package SerilogWeb.Classic
dotnet add package SerilogWeb.Classic
<PackageReference Include="SerilogWeb.Classic" Version="5.*" />
paket add SerilogWeb.Classic
The package includes automatic HTTP request and response logging as well as some Serilog enrichers. Unless you are trying to debug a specific problem with your website, we recommend disabling HTTP logging since that will produce a lot of messages (depending on the traffic on your website). HTTP logging can be disabled by including the following code in the Global.asax.cs
file:
protected void Application_Start()
{
SerilogWebClassic.Configure(cfg => cfg
.Disable()
);
// ...
}
To enrich log messages with HTTP contextual information you can configure one or more enrichers in the same place as you configure the elmah.io sink:
Log.Logger = new LoggerConfiguration()
.WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
.Enrich.WithHttpRequestClientHostIP()
.Enrich.WithHttpRequestRawUrl()
.Enrich.WithHttpRequestType()
.Enrich.WithHttpRequestUrl()
.Enrich.WithHttpRequestUserAgent()
.Enrich.WithUserName(anonymousUsername:null)
.CreateLogger();
This will automatically fill in fields on elmah.io like URL, method, client IP, and UserAgent.
Check out this full sample for more details.
Config using appsettings.json
While Serilog provides a great fluent C# API, some prefer to configure Serilog using an appsettings.json
file. To configure the elmah.io sink this way, you will need to install the Serilog.Settings.Configuration
NuGet package. Then configure elmah.io in your appsettings.json
file:
{
// ...
"Serilog":{
"Using":[
"Serilog.Sinks.ElmahIo"
],
"MinimumLevel": "Warning",
"WriteTo":[
{
"Name": "ElmahIo",
"Args":{
"apiKey": "API_KEY",
"logId": "LOG_ID"
}
}
]
}
}
Note
Make sure to specify the apiKey
and logId
arguments with the first character in lowercase.
Finally, tell Serilog to read the configuration from the appsettings.json
file:
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Extended exception details with Serilog.Exceptions
The more information you have on an error, the easier it is to find out what went wrong. Muhammad Rehan Saeed made a nice enrichment package for Serilog named Serilog.Exceptions
. The package uses reflection on a logged exception to log additional information depending on the concrete exception type. You can install the package through NuGet:
Install-Package Serilog.Exceptions
dotnet add package Serilog.Exceptions
<PackageReference Include="Serilog.Exceptions" Version="5.*" />
paket add Serilog.Exceptions
And configure it in C# code:
var logger = new LoggerConfiguration()
.Enrich.WithExceptionDetails()
.WriteTo.ElmahIo(/*...*/)
.CreateLogger();
The elmah.io sink will automatically pick up the additional information and show them in the extended message details overlay. To navigate to this view, click an error on the search view. Then click the button in the upper right corner to open extended message details. The information logged by Serilog.Exceptions
are available beneath the Data tab.
Remove sensitive data
Structured logging with Serilog is a great way to store a lot of contextual information about a log message. In some cases, it may result in sensitive data being stored in your log, though. We recommend you remove any sensitive data from your log messages before storing them on elmah.io and anywhere else. To implement this, you can use the OnMessage
event as already shown previously in the document:
OnMessage = msg =>
{
foreach (var d in msg.Data)
{
if (d.Key.Equals("Password"))
{
d.Value = "****";
}
}
}
An alternative to replacing sensitive values manually is to use a custom destructuring package for Serilog. The following example shows how to achieve this using the Destructurama.Attributed
package:
Install-Package Destructurama.Attributed
dotnet add package Destructurama.Attributed
<PackageReference Include="Destructurama.Attributed" Version="2.*" />
paket add Destructurama.Attributed
Set up destructuring from attributes:
Log.Logger = new LoggerConfiguration()
.Destructure.UsingAttributes()
.WriteTo.ElmahIo(/*...*/)
.CreateLogger();
Make sure to decorate any properties including sensitive data with the NotLogged
attribute:
public class LoginModel
{
public string Username { get; set; }
[NotLogged]
public string Password { get; set; }
}
Setting a category
elmah.io provide a field named Category to better group log messages by class name, namespace, or similar. Category maps to Serilog's SourceContext automatically when using Serilog.Sinks.ElmahIo
. To make sure that the category is correctly populated, either use the Forcontext
method:
Log.ForContext<Program>().Information("This is an information message with context");
Log.ForContext("The context").Information("This is another information message with context");
Or set a SourceContext
or Category
property using LogContext
:
using (LogContext.PushProperty("SourceContext", "The context"))
Log.Information("This is an information message with context");
using (LogContext.PushProperty("Category", "The context"))
Log.Information("This is another information message with context");
When using Serilog through Microsoft.Extensions.Logging's ILogger<T>
interface, the source context will automatically be set by Serilog.
Serilog Troubleshooting
Here are some things to try out if logging from Serilog to elmah.io doesn't work:
- Run the
diagnose
command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation. - Make sure that you have the newest
Serilog.Sinks.ElmahIo
andElmah.Io.Client
packages installed. - Make sure to include all of the configuration from the example above.
- Make sure that the API key is valid and allow the Messages | Write permission.
- Make sure to include a valid log ID.
- Make sure that you have sufficient log messages in your subscription and that you didn't disable logging to the log or include any ignore filters/rules.
- Always make sure to call
Log.CloseAndFlush()
before exiting the application to make sure that all log messages are flushed. - Set up Serilog's SelfLog to inspect any errors happening inside Serilog or the elmah.io sink:
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
. - Implement the
OnError
action and put a breakpoint in the handler to inspect if any errors are thrown while logging to the elmah.io API.
PeriodicBatchingSink is marked as sealed
If you get a runtime error stating that the PeriodicBatchingSink
class is sealed and cannot be extended, make sure to manually install version 3.1.0
or newer of the Serilog.Sinks.PeriodicBatching
NuGet package. The bug has also been fixed in the Serilog.Sinks.ElmahIo
NuGet package from version 4.3.29
and forward.
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