OpenTelemetry Logs for .NET

This document covers OpenTelemetry Logs for .NET, focusing on Microsoft.Extensions.Logging.

Prerequisites

Make sure your exporter is configured before you start instrumenting code. Follow Getting started with OpenTelemetry .NET or set up Direct OTLP Configuration first.

If you are not familiar with logs terminology like structured logging or log-trace correlation, read the introduction to OpenTelemetry Logs first.

Overview

OpenTelemetry provides two approaches for collecting logs in .NET:

  1. Logger provider (recommended): Integrate with Microsoft.Extensions.Logging to capture logs and correlate them with traces.
  2. Logs API: Use the native OpenTelemetry Logs API directly for maximum control.

The logger provider approach is recommended because it preserves your existing logging patterns while adding trace context automatically.

ASP.NET Core logging

Use AddOpenTelemetry() to configure logging in ASP.NET Core:

cs
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var dsn = Environment.GetEnvironmentVariable("UPTRACE_DSN");
if (string.IsNullOrEmpty(dsn))
{
    throw new Exception("UPTRACE_DSN environment variable is required");
}

var serviceName = "myservice";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion)
            .AddAttributes(new Dictionary<string, object>
            {
                ["deployment.environment"] = builder.Environment.EnvironmentName
            }));
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
    logging.AddOtlpExporter(options =>
    {
        options.Endpoint = new Uri("https://api.uptrace.dev/v1/logs");
        options.Headers = $"uptrace-dsn={dsn}";
        options.Protocol = OtlpExportProtocol.HttpProtobuf;
    });
});

var app = builder.Build();
app.MapGet("/", (ILogger<Program> logger) =>
{
    logger.LogInformation("Request handled by {Service}", serviceName);
    return "ok";
});
app.Run();

Install the required packages:

shell
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Logs

Console application logging

For console apps or background services, use LoggerFactory directly:

cs
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var dsn = Environment.GetEnvironmentVariable("UPTRACE_DSN");
if (string.IsNullOrEmpty(dsn))
{
    throw new Exception("UPTRACE_DSN environment variable is required");
}

var serviceName = "myservice";
var serviceVersion = "1.0.0";

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.SetResourceBuilder(
            ResourceBuilder.CreateDefault()
                .AddService(serviceName: serviceName, serviceVersion: serviceVersion));
        logging.IncludeFormattedMessage = true;
        logging.IncludeScopes = true;
        logging.AddOtlpExporter(options =>
        {
            options.Endpoint = new Uri("https://api.uptrace.dev/v1/logs");
            options.Headers = $"uptrace-dsn={dsn}";
            options.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    });
});

var logger = loggerFactory.CreateLogger("Example");
logger.LogInformation("Background job started");

Log-trace correlation

When you log inside an active span, OpenTelemetry includes trace context automatically:

cs
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Trace;

var dsn = Environment.GetEnvironmentVariable("UPTRACE_DSN");
if (string.IsNullOrEmpty(dsn))
{
    throw new Exception("UPTRACE_DSN environment variable is required");
}

var activitySource = new ActivitySource("app_or_package_name");

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(activitySource.Name)
    .AddOtlpExporter(options =>
    {
        options.Endpoint = new Uri("https://api.uptrace.dev/v1/traces");
        options.Headers = $"uptrace-dsn={dsn}";
        options.Protocol = OtlpExportProtocol.HttpProtobuf;
    })
    .Build();

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter(options =>
        {
            options.Endpoint = new Uri("https://api.uptrace.dev/v1/logs");
            options.Headers = $"uptrace-dsn={dsn}";
            options.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    });
});

var logger = loggerFactory.CreateLogger("Example");

using var activity = activitySource.StartActivity("process-request");
logger.LogInformation("Processing request for {UserId}", "12345");

In Uptrace, the log record is linked to the trace and span where it was emitted.

Best practices

Use structured logs

Prefer structured fields over string concatenation:

cs
logger.LogInformation("Order processed {OrderId} {Status}", 42, "ok");

Record exceptions

Attach exceptions so that stack traces are preserved:

cs
try
{
    throw new InvalidOperationException("Something went wrong");
}
catch (Exception ex)
{
    logger.LogError(ex, "Processing failed");
}

What's Next?