C#
Sam Lau  

Application Insights and Serilog in .NET 8 Worker Services with HostApplicationBuilder

Since .NET 8, the template of Worker Services has changed from using IHostBuilder to HostApplicationBuilder.

.NET 7

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
    .Build();

host.Run();

.NET 8

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();

As a result, the way to implement Application Insights and Serilog in Worker Service has changed a bit and I am going to go through it.

NuGet packages

These are the packages you need. You may want to add extra packages based on your need, such as different sinks (likely Serilog.Sinks.Console) or enrichers (eg. Serilog.Enrichers.Environment).

  • Microsoft.ApplicationInsights.WorkerService
  • Serilog
  • Serilog.Extensions.Hosting
  • Serilog.Settings.Configuration
  • Serilog.Sinks.ApplicationInsights

appsettings.json

This is the appsettings.json you need. Substitute you Application Insights connection string and application name. Play around with the minimum log level if you want to. Note that Serilog does not read configuration from Logging so you can remove it.

{
  "ApplicationInsights": {
    "ConnectionString": "<YOUR_CONNECTION_STRING>"
  },
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning"
      }
    },
    "Properties": {
      "ApplicationName": "My Worker Service App"
    }
  }
}

Program.cs

In Program.cs, you need to add builder.Services.AddApplicationInsightsTelemetryWorkerService(); to register Application Insights telemetry. This read ConnectionString from appsettings.json to connect to Application Insights so make sure that is correct. This inject TelemetryConfiguration, which will be used by Serilog to send log to Application Insights. It also inject TelemetryClient, which you can use to send telemetry, but we are not using it in this example.

The next thing you need to add is builder.Services.AddSerilog(). The line configuration.ReadFrom.Configuration(builder.Configuration); loads your Serilog configuration from appsetting.json. The sample I am showing configure the sink to Application Insights in code but you can also set that up in appsetting.json, which you can easily find tutorial online on how to do it so I am not showing it here. The reason I am doing it in code is because the actual code I wrote has a logic to only send log to Application Insights in release mode and sink to console if in debug mode.

using Microsoft.ApplicationInsights.Extensibility;
using Serilog;
using MyWorkerService;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddApplicationInsightsTelemetryWorkerService();

builder.Services.AddSerilog((serviceProvider, configuration) =>
{
    configuration.ReadFrom.Configuration(builder.Configuration);
    configuration.WriteTo.ApplicationInsights(
        serviceProvider.GetRequiredService<TelemetryConfiguration>(),
        TelemetryConverter.Traces);
});

var host = builder.Build();
host.Run();

Worker.cs or any classes

This is the easy part. You just need to dependency inject ILogger<Worker> or ILogger<T> in any class you want to log and log away! I actually did not modify one bit from the Worker Services template so there you go.

namespace MyWorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                }
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}