Table of Contents
Even though the built-in logging in .NET Core is pretty good these days, Serilog takes it to the next level, making complex logging exceptionally easy.
Serilog allows us to not only log the output of the functions to the Console but also write to a Log file, Application Insights, Raygun, Sentry, or any number of locations, called Sinks.
You can take a look at the long list of available sinks at https://github.com/serilog/serilog/wiki/Provided-Sinks
In this post, we’ll just look at setting up a basic configuration for Serilog. You can then look further into any sinks that you might be interested in.
Azure Functions project set up
These instructions are for use with the Isolated Model for Azure Functions. From .NET 6 onwards, the isolated model has been the preferred model and will be the only model beyond .NET 8.
Nuget Packages for Serilog
The first step once you’ve created your Azure Functions project is to add a few Nuget packages. Add the following Nuget packages.
Serilog
Serilog.Extensions.Logging
Serilog.Sinks.Console
Serilog.Sinks.File
Base Serilog Configuration
Once these have been added, we can switch over to the Program.cs to start wiring up the Serilog logging. The minimum configuration that will write log records to the Console, which is the out-of-the-box experience for an Azure Functions project is below.
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureLogging((hostingContext, logging) =>
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(LogEventLevel.Debug)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
host.Run();
Above we’ve created our LoggerConfiguration and added the Console sink to it, then created the Logger and added the Logger to the Logging Pipeline.
We can certainly improve on this though by using a few additional features of Serilog.
Adding a second Serilog Sink
We can add a second sink to log to a txt file and rotate our text files on a daily basis.
When we run the Azure Function app on Azure, as opposed to locally, we need to write the log files to the LogFiles folder path, not to the local execution directory.
So to accomplish this, we do a quick check to see if we’re running on Azure. Checking an Environment variable that will only exist on Azure will tell us if we’re running on Azure or locally.
We then use this to set the appropriate file name and location.
The Serilog file sink will automatically append a date stamp on the end of the filename when we set the rollingInterval.
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureLogging((hostingContext, logging) =>
{
var filepath = string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("WEBSITE_CONTENTSHARE")) ?
"log.txt" :
@"D:\home\LogFiles\Application\log.txt";
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(LogEventLevel.Debug)
.WriteTo.File(filepath, LogEventLevel.Debug, rollingInterval: RollingInterval.Day)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
host.Run();
Using Serilog Enrichment
But we can do better than this as well. We can add some additional enrichment to the logs for when we send the logs to more complex systems like Application Insights, Seq, Rayrun, and Sentry to name a few.
You can read more about Enrichment at https://github.com/serilog/serilog/wiki/Enrichment
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureLogging((hostingContext, logging) =>
{
var filepath = string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("WEBSITE_CONTENTSHARE")) ?
"log.txt" :
@"D:\home\LogFiles\Application\log.txt";
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Application", "SHDev Blog Functions")
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Debug)
.WriteTo.File(filepath, LogEventLevel.Debug, rollingInterval: RollingInterval.Day)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
host.Run();
Now this will give a nice logging experience and allow us to log to both the Console and to a File.
Minimum Logging Levels
But once you start adding in some triggers and binding to other services in your functions, you’ll see that the logging starts to explode with log entries from the included services.
Azure Storage services like Queues, Tables, and Blobs emit a LARGE number of extra logs, which we want to suppress.
To do this, we can set some Minimum Level overrides based on the Namespace or Type.
Below is what I’ve found works best for me. It shows me the important things like errors but hides the firehose of logging that I don’t need to see.
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureLogging((hostingContext, logging) =>
{
var filepath = string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("WEBSITE_CONTENTSHARE")) ?
"log.txt" :
@"D:\home\LogFiles\Application\log.txt";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Worker", LogEventLevel.Warning)
.MinimumLevel.Override("Host", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Error)
.MinimumLevel.Override("Function", LogEventLevel.Debug)
.MinimumLevel.Override("Azure.Storage", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Identity", LogEventLevel.Error)
.Enrich.WithProperty("Application", "SHDev Blog Functions")
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Debug)
.WriteTo.File(filepath, LogEventLevel.Debug, rollingInterval: RollingInterval.Day)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
host.Run();
This is the default setup that I use when creating a new Azure Functions project.
I might also look at adding in Application Insights or Raygun depending on the needs of the project.
First thank you for your article . It is very helpful
Question
in Program.cs
in string
var filepath = string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(“WEBSITE_CONTENTSHARE”)) ?
“log.txt” :
@”D:\home\LogFiles\Application\log.txt”;
is it correct? Condition must be opposite.
No, this is correct. If the WEBSITE_CONTENTSHARE environment variable is empty, then I must be running locally, so just use log.txt. If it’s not empty, then it’s running on Azure, so use the specific path to the standard Azure App Service Application Logs folder.
I have Serilog working locally with values in local.settings.json instead of hard coding the settings in program.cs. However I cannot figure out how to translate the local.settings.json to the Azure Function Envriornment Variables in the Azure Portal
Sorry for the late reply. I’ve used this before to do it as it properly sets up array values
https://github.com/AquilaSands/json-to-azure-app-settings