[HOWTO] Configure Serilog for a .NET Core Web API running on Azure App Service

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:

  1. Log in to the Azure Portal
  2. Switch to the corresponding directory (Azure tenant)
  3. Search for Application Insights in the search bar on the top
  4. Select the application insights resource the app sends its logs to
  5. Navigate to Logs in section Monitoring in the menu on the left
  6. 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
Traces in Azure Application Insights

Exceptions in Azure Application Insights

Log Stream

To stream the application logs in near-realtime, proceed as follows:

  1. Log in to the Azure Portal
  2. Switch to the corresponding directory (Azure tenant)
  3. Search for App Service in the search bar on the top
  4. Select the app service resource the app runs on
  5. Navigate to Log stream in section Monitoring 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.

2 thoughts on “[HOWTO] Configure Serilog for a .NET Core Web API running on Azure App Service

Add yours

  1. 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?

    Like

  2. 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.

    Like

Leave a comment

Website Powered by WordPress.com.

Up ↑