In this blog post and the underlying example, Serilog is configured for Linux Azure App Services.
Recently I wanted to check the logs of a .NET Core Web API that uses Serilog and runs on an Azure App Service. It took me a while to find the logs in Azure Application Insights. After that I wanted to check the log stream of the Azure App Service to get near-realtime insights. Unfortunately without success. The reason for this was that Serilog was not configured correctly. Consequently, the logs have not appeared in the log stream. In order to save myself and others this trouble in the future, I have decided to share and briefly describe the configuration I worked out after a lot of testing and trial and error.
Logging Requirements
Let’s start with some generally applicable requirements that I have used as a guide for implementation.
- If the application runs on local development environment, logs should be logged to the console (i.e. if started in Visual Studio or Visual Studio Code)
- If the application runs on an App Service in the Azure Cloud, logs should be logged to …
- … Application Insights
- … App Service Log Stream
Implementation
Below I will explain the implementation with which I was able to fulfill the logging requirements. You find the implementation in the following GitHub repository which contains a simple .NET project I created based on Visual Studio project template ASP.NET Core Web API
.
aspnetcore-serilog-azureappservice
The .NET Core Web API uses ILogger
of NuGet package Microsoft.Extensions.Logging
. ILogger
is injected into the constructor of the class that needs to log. The underlying logging framework is Serilog. Serilog is configured in Program.cs
and appsettings.json
of the application (appsettings.Development.json
for local development environment as in IDEs in comparison to Azure App Service, ASPNETCORE_ENVIRONMENT
is set by default to Development
).
Let’s have a look at the code.
Program.cs
using Serilog;
#pragma warning disable CA1852 // Seal internal types
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateBootstrapLogger();
#pragma warning restore CA1852 // Seal internal types
try
{
Log.Information("Starting web host");
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog by reading config from appsettings.json
builder.Host
.UseSerilog((context, loggerConfiguration) =>
loggerConfiguration.ReadFrom.Configuration(context.Configuration));
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
catch (Exception ex) when (ex.GetType().Name is not "StopTheHostException" &&
ex.GetType().Name is not "HostAbortedException")
{
Log.Fatal(ex, "Unhandled exception");
}
finally
{
Log.Information("Shut down complete");
Log.CloseAndFlush();
}
.CreateBootstrapLogger()
method configures Serilog immediately when the application starts. This allows us to catch and report exceptions thrown during set-up of the ASP.NET Core host. For more details see serilog-aspnetcore – Two-stage initialization.
As you can see in line 18, the logger configuration is read from appsettings.json
and from the environment specific appsettings (i.e. appsettings.Development.json
).
appsettings.Development.json
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} ({SourceContext}){NewLine}{Exception}"
}
}
]
}
}
For local development environment (i.e. when running the application in Visual Studio or Visual Studio Code), Serilog console sink in combination with a custom outputTemplate
is used to log the application logs to the console. Section MinimumLevel
let’s you define the default log level and also let’s you override log levels of specific namespaces. Furthermore the logs get enriched with thread id, machine name and information from log context where you can push additional properties to.
appsettings.json
{
"Serilog": {
"Using": [ "Serilog.Sinks.ApplicationInsights" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"WriteTo": [
{
"Name": "ApplicationInsights",
"Args": {
"telemetryConverter": "Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
}
},
{
"Name": "File",
"Args": {
"path": "../../LogFiles/_application-logs.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}",
"rollOnFileSizeLimit": true,
"fileSizeLimitBytes": 4194304,
"retainedFileCountLimit": 5
}
}
]
},
"AllowedHosts": "*"
}
For production environments – in this case Azure App Service – ApplicationInsights
sink is used. The telemetryConverter
specifies the converter that is used to convert Serilog logs to Application Insights telemetry data. Here we make use of the TraceTelemetryConverter
provided by Serilog. This leads to Serilog creating traces in Application Insights (TraceTelemetry
) for log levels defined in section MinimumLevel
. However, if the log event contains any exceptions it will always be sent as ExceptionTelemetry
to Application Insights.
Additionally Serilog logs are logged to a file. The most important setting in File
section is the path. According to the docs, any information written to the console output or files ending in .txt
, .log
, or .htm
that are stored in the /home/LogFiles
directory is streamed by App Service – so you have to ensure that the log file is in the correct location.
Azure App Service Configuration
To make the integration with Application Insights work, ensure that the following application setting is set in the Azure App Service.
- Name:
APPLICATIONINSIGHTS_CONNECTION_STRING
- Value: connection string of the app insights resource the logs should be sent to
Important: Application Insights instrumentation keys should not be used anymore! For more details see here.
View Logs
This section contains step-by-step manuals about how to query application logs logged with Serilog in Application Insights and how to stream them using Log Stream
feature of Azure App Service.
Console
Application Insights
To query the application logs in Azure Application Insights, proceed as follows:
- Log in to the Azure Portal
- Switch to the corresponding directory (Azure tenant)
- Search for
Application Insights
in the search bar on the top - Select the application insights resource the app sends its logs to
- Navigate to
Logs
in sectionMonitoring
in the menu on the left - Enter one of the following queries in the query editor and click
Run
:
- To query non exception logs
traces
| where message startswith "START_OF_LOG_MESSAGE"
- To query exception logs
exceptions
Log Stream
To stream the application logs in near-realtime, proceed as follows:
- Log in to the Azure Portal
- Switch to the corresponding directory (Azure tenant)
- Search for
App Service
in the search bar on the top - Select the app service resource the app runs on
- Navigate to
Log stream
in sectionMonitoring
in the menu on the left
Important: Make sure you check the whole stream for your logs as they seem to not be ordered by date but by log file.
Great post! have some questions:
For PRODUCTION, you said “According to the docs, any information written to the console output or files ending in…” so why not just use CONSOLE as a sink for production, instead of FILE sink?
For PRODUCTION you send data to Application Insights. Does this setup mean we can also do a LIVE METRICS?
LikeLike
Thanks!
I’ll try to answer your questions. Using console sink would also be possible. I wanted to use file sink to be able to donwload the latest logs in case for some reason there is something broken with App Insights Integration.
Have to check, if live metrics data is also sent by this setup. I assume so.
LikeLike