OpenTelemetry With Application Insights

In order to follow the direction of the industry, Azure Monitor and Application Insights is moving towards OpenTelemetry. So how would one setup a minimal error logging?

You can go all-in with logging, tracing and start collecting metrics and all that with just few lines of code, but one should be careful - you can very quickly blast through the free quota and any limits you initially thought were reasonable. So if you decide to stick with the defaults, monitoring your spend is crucial.

If you want something minimal just for error logging, the documentation is not quite clear, or rather not quite there yet. Following is my initial setup for .NET 8. I’m expecting the setup will need to be adjusted as things evolve.

Starting with some nuget packages

<ItemGroup>
    <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.2.0" />
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
</ItemGroup>

For appsettings.json config, you might want to override the Warning log levels in appsettings.Development.json to Information

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    },
    "OpenTelemetry": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "AzureMonitor": {
    "ConnectionString": ""
  }
}

For Program.cs, assuming everything will go inside the Program class.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Azure.Monitor.OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace Samples.OTEL;

public class Program
{
    // ...
}

For the initial setup phase, it’s good to configure at least basic logging so you can have something as a fallback when things are not starting up.

public static void Main(string[] args)
{
    // simple logging for the init phase
    using var loggerFactory = LoggerFactory
        .Create(builder => builder
            .AddOpenTelemetry(logging => logging.AddConsoleExporter()));
    
    var initLogger = loggerFactory.CreateLogger("Program");
    initLogger.LogInformation("Starting...");

// ...

Still in Main(), after the app is built, switching to the, now fullly configured, logging

// ...

ILogger<Program> fullLogger = null;

try
{
    var builder = WebApplication.CreateBuilder(args);

    // register logging to services
    ConfigureLogging(builder);

    var app = builder.Build();

    // switch to full logger
    initLogger.LogInformation("App built, switching to global logger ...");
    fullLogger = app.Services.GetRequiredService<ILogger<Program>>();

    app.Run();
}
catch (Exception ex)
{
    if (fullLogger is null)
    {
        initLogger.LogCritical(ex, "init failure, exiting ...");
    }
    else
    {
        fullLogger.LogCritical(ex, "total failure, exiting ...");
    }
}

Next - the key part. As mentioned at the start, this is meant just for very minimal amount of logging.

For development purposes, there is also an initial cleanup, so the logs are not too noisy. Formatting, code structure and inclusion/exclusion of the console is subjective and will depend on your needs. This can serve as a starting point.

private static void ConfigureLogging(WebApplicationBuilder builder)
{
    if (builder.Environment.IsDevelopment())
    {
        builder.Logging.ClearProviders();
    }

    builder.Logging.AddOpenTelemetry(options =>
    {
        var appName = builder.Configuration.GetValue<string>("App:AppName");
        var resourceBuilder = ResourceBuilder
            .CreateDefault()
            .AddService(appName);
        options.SetResourceBuilder(resourceBuilder);

        // add console logger
        if (builder.Environment.IsDevelopment())
        {
            options.AddConsoleExporter();
        }

        // azure monitor logger
        if (builder.Environment.IsDevelopment() == false)
        {
            options.AddAzureMonitorLogExporter(azureOptions =>
            {
                azureOptions.ConnectionString = builder.Configuration
                    .GetValue<string>("AzureMonitor:ConnectionString");

                // disable unnecessary features
                azureOptions.Diagnostics.IsTelemetryEnabled = false;
                azureOptions.Diagnostics.IsLoggingContentEnabled = false;
                azureOptions.Diagnostics.IsDistributedTracingEnabled = false;
            });
        }

        options.IncludeScopes = true;
    });
}

I’m quite happy with how the logging currently works. I have quick access to full stack traces, all the extra added scopes are there and also correlation, which helps with tracking problems between multiple services.

Comments

comments powered by Disqus