Serilog with AppInsights and OpenTelemetry

LaasikLaasik
3 min read

Yes, the cover picture is made with DALL-E.

Again another reference for myself, as it seems that I tend to rewrite bunch of code every time I need to start another .net core backend service. And I tend to also go out and look every time how to integrate with Serilog.

First, I haven’t found that out of the box Azure Application Insights provides any OpenTelemetry ingestion endpoints. So for my use cases the Serilog OpenTelemetry sink provides almost no value at all. Unless, one wants to run the Aspire dashboard, which actually has an OpenTelemetry endpoint and which provides a quite OK structured log and metrics consolidation place during dev.

Anyway. Approach to enabling Serilog and pushing the logs thorugh to the Application Insights and Open Telemetry.

Add relevant packages, there are a load, if you want to log down and enrich a bunch of things, my current nugget package list is something of (ignore the versions and make sure you update):

<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
<PackageReference Include="Serilog.Enrichers.CorrelationId" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Exceptions.EntityFrameworkCore" Version="8.4.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.1.1" />

Then as I have started to hate the Program.cs which is full of loads of configurations, I’ve started to move the individual components that would require more than some configuration to their own extension classes. So e.g AddSerilogLogging.

public static IServiceCollection AddSerilogLogging(
    this IServiceCollection services, 
    Func<IConfiguration> configuration)
{
    var config = configuration();
    services.AddSerilog((ctx, opts) =>
    {
        // I prefer to configure in the config file, though it would be nice to have a
        // JSON schema file for serilog :D.
        opts.ReadFrom.Configuration(config);

        // Logs to visual studio or whatever other Debug sink, if the debugger is attached.
        // Very neat feature.
        if (System.Diagnostics.Debugger.IsAttached)
        {
            opts.WriteTo.Debug();
        }

        // Check if open telemetry env variables are set, if they are 
        // enable the OpenTelemetry logging. This method reads config
        // out from the 
        if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")))
        {
            opts.WriteTo.OpenTelemetry();
        }
        // If app insights is configured, get the actual telemetry amd configure it
        if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING")))
        {
            var telemetry = ctx.GetRequiredService<TelemetryConfiguration>();
            opts.WriteTo.ApplicationInsights(telemetry, TelemetryConverter.Traces);
        }
    });

    return services;
}

And then configure the relevant sections in the Program.cs

 public class Program
 {
     public static void Main(string[] args)
     {
         Log.Logger = new LoggerConfiguration()
             .Enrich.FromLogContext()
             .Enrich.WithMachineName()
             .Enrich.WithEnvironmentName()
             .WriteTo.File("app-startup.log", fileSizeLimitBytes: 10*1024*1024, rollOnFileSizeLimit: true, retainedFileCountLimit: 5)
             .WriteTo.Console()
             .CreateBootstrapLogger();
         try
         {
             var builder = WebApplication.CreateBuilder(args);
             builder.Services.AddSerilogLogging(() => builder.Configuration);
             var app = builder.Build();

             // Configure the HTTP request pipeline.
             app.Run();
         }
         catch(Exception ex)
         {
             Log.Fatal(ex, "Host terminated unexpectedly");
         }
         finally
         {
             Log.CloseAndFlush();
         }
     }
 }

And then in the appsettings.json configure it to look something like

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Information",
        "System": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
      },
      {
        "Name": "File",
        "Args": {
          "path": "Logs/foobar.log",
          "rollingInterval": "Day"
        }
      }
    ],
    "Enrich": [
      "FromLogContext",
      "WithMachineName",
      "WithThreadId",
      "WithProcessId",
      "WithProcessName",
      "WithRequestId"
    ]
  }
}
0
Subscribe to my newsletter

Read articles from Laasik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Laasik
Laasik