Search code examples
c#.net-coreazure-application-insightsserilog

ITelemetryInitializer.Initialize never being called when using Serilog with ApplicationInsights in Console application


I am configuring DI as follows in my .NET Core 3.1 Console application:

services.AddSingleton<ITelemetryInitializer, TelemetryInitializer>();
services.AddApplicationInsightsTelemetryWorkerService(instrumentationKey);

In appSettings.json I have:

"Serilog": {
  "Using": [
    "Serilog.Sinks.Console",
    "Serilog.Sinks.ApplicationInsights"
  ],
  "MinimumLevel": "Information",
  "WriteTo": [
    {
      "Name": "Console",
      "Args": {
        "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
        "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
      }
    },
    {
      "Name": "ApplicationInsights",
      "Args": {
        "restrictedToMinimumLevel": "Information",
        "InstrumentationKey": "...",
        "telemetryConverter": "Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights",
        "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
        "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
      }
    }
  ],
  "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
}

Serilog is configured as follows:

Host.CreateDefaultBuilder(args)
    .UseSerilog((hostingContext, loggerConfiguration) =>
        loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration)
    )
    .UseConsoleLifetime()
    .Build()
    .Run()

My initializer class:

public class TelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        telemetry.Context.Cloud.RoleName = "something-something";
    }
}

The Initialize method however is not being called when I log using ILogger. If I resolve TelemetryClient directly and log using that, Initialize is called.

Am I missing some configuration for Serilog?


Solution

  • It seems to be a glitch of Serilog.Sinks.ApplicationInsights. When initializing sink from config (code reference), it creates a new TelemetryClient which results in an active TelemetryConfiguration before the registered TelemetryInitializer could be associate with it.

    To get the TelemetryInitilizer injected with Active configuration, it needs to have the TelemetryClient instance created once before the Serilog initializes the App Insights sink. Otherwise it would be too late to respect it since internally AddApplicationInsightsTelemetryWorkerService registers TelemetryClient as Singleton. So below is my working code which just makes a dummy resolve of TelemetryClient before initializing loggerConfiguration of Serilog.

    public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureServices((hostContext, services) =>
                    {
                        services.AddApplicationInsightsTelemetryWorkerService(instrumentationKey);
                        services.AddSingleton<ITelemetryInitializer, TelemetryInitializer>();
                        services.AddHostedService<Worker>();
                    })
                    .UseSerilog((hostingContext, serviceProvider, loggerConfiguration) => {
                        serviceProvider.GetRequiredService<TelemetryClient>(); // just a dummy resolve
                        loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration);
                    })
                    .UseConsoleLifetime();
        }
    

    Option 2 is to create a custom TelemetryConverter which can update required context instead of using the Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter. That would remove the reliance on initializer.

    Option 3 is to switch to code based setup instead of configuration which would allow you to pass TelemetryClient or TelemetryConfiguration instance like https://github.com/serilog/serilog-sinks-applicationinsights#configuring. But that may not be as declarative as configuration based approach.