Sarıkaya Dev Logo

Unified Logging for .NET 8 Microservices with Serilog, OpenTelemetry, and Azure Log Analytics

Mahmut Sarıkaya 4 min read 10 Views 0
Unified Logging for .NET 8 Microservices with Serilog, OpenTelemetry, and Azure Log Analytics

Why unified logging matters for .NET 8 microservices

Imagine a fleet of ten independent services handling orders, payments, and notifications. When a single transaction fails, the root cause is often buried in scattered log files, each using a different format. In 2023, a survey by the Cloud Native Computing Foundation reported that 58% of teams spent more than 30 minutes per incident just locating the relevant log entry. A unified logging pipeline eliminates that waste by standardising structure, enriching context, and routing everything to a central store.

Choosing the right stack: Serilog, OpenTelemetry, Azure Log Analytics

Serilog provides flexible, structured logging for .NET, while OpenTelemetry adds distributed tracing and metrics without locking you into a vendor. Azure Log Analytics acts as the aggregation point, offering powerful Kusto queries and visual dashboards. The three components complement each other: Serilog emits JSON events, OpenTelemetry injects correlation identifiers, and Azure Log Analytics stores and analyses the data at scale.

Setting up Serilog in a .NET 8 service

Start with the minimal hosting model introduced in .NET 6. Add the Serilog packages Serilog.AspNetCore and Serilog.Sinks.AzureAnalytics. The following code configures console output for local debugging and the Azure sink for production.

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, lc) => lc
    .WriteTo.Console()
    .WriteTo.AzureAnalytics(
        workspaceId: "YOUR_WORKSPACE_ID",
        authenticationId: "YOUR_SHARED_KEY"));
var app = builder.Build();
app.Run();

Replace YOUR_WORKSPACE_ID and YOUR_SHARED_KEY with the values from the Azure portal. The sink automatically batches events, keeping network overhead below 5 KB per second even under heavy load.

Instrumenting with OpenTelemetry

OpenTelemetry captures trace spans and propagates a traceparent header across HTTP calls. Add the OpenTelemetry.Extensions.Hosting and OpenTelemetry.Exporter.Jaeger packages, then register the tracer in Program.cs. The example below creates a service‑named OrderService and exports traces to a local Jaeger collector.

builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
{
    tracerProviderBuilder
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSource("MyCompany.Services")
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OrderService"))
        .AddJaegerExporter(opts =>
        {
            opts.AgentHost = "localhost";
            opts.AgentPort = 6831;
        });
});

When a request enters the API gateway, OpenTelemetry generates a root span, and each downstream microservice creates child spans automatically. The correlation ID is added to Serilog’s log context in the next step.

Sending logs to Azure Log Analytics

To bind trace IDs to log entries, enrich Serilog with the Serilog.Enrichers.ActivityTraceId package. This adds TraceId and SpanId properties to every JSON event, enabling cross‑resource queries in Log Analytics.

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .Enrich.WithActivityTraceId()
    .WriteTo.AzureAnalytics(
        workspaceId: "YOUR_WORKSPACE_ID",
        authenticationId: "YOUR_SHARED_KEY",
        logName: "MicroserviceLogs")
    .CreateLogger();

After deployment, navigate to the Azure portal, open Log Analytics, and run a Kusto query such as:

MicroserviceLogs
| where TraceId == "{trace-id-from-request}" 
| order by Timestamp desc

This returns all log lines that participated in the same distributed transaction, regardless of which container produced them.

Correlating traces and logs across services

Consider an order creation workflow that touches OrderService, PaymentService, and NotificationService. Each service logs an event with the shared TraceId. In Log Analytics you can visualise the end‑to‑end path:

let trace = "{trace-id}";
MicroserviceLogs
| where TraceId == trace
| project Timestamp, Service = SourceContext, Message, SpanId, ParentSpanId
| order by Timestamp asc

The result is a chronological view that matches the OpenTelemetry trace diagram, turning a multi‑minute investigation into a few seconds of insight.

Best practices and performance tips

1. **Batch size** – Azure Analytics batches 1 MB or 5 seconds, whichever comes first. Adjust period and batchPostingLimit only if you have strict latency requirements.
2. **Log level hygiene** – Use Information for business events, Debug for internal state, and reserve Error for exceptions. Over‑logging inflates storage costs; a 30‑day retention at 1 GB per day costs roughly $30 in Azure.
3. **Exception enrichment** – Wrap exceptions with Serilog.Exceptions to capture stack traces, inner exceptions, and custom data fields.
4. **Secure credentials** – Store workspaceId and authenticationId in Azure Key Vault and inject them via builder.Configuration at runtime.

Conclusion

Unified logging for .NET 8 microservices is no longer a luxury; it is a necessity for rapid incident resolution and compliance reporting. By pairing Serilog’s structured output with OpenTelemetry’s trace correlation and Azure Log Analytics’ query engine, teams gain a single pane of glass that scales from local development to production clusters of hundreds of containers. Implement the steps above, respect the best‑practice checklist, and you’ll turn noisy log streams into actionable intelligence.

Sources

  • Microsoft Docs – Azure Monitor Log Analytics
  • Serilog Documentation – Sinks and Enrichers
  • OpenTelemetry .NET – Tracing Overview

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #Serilog #OpenTelemetry #.NET 8 #microservices logging #Azure Log Analytics
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

4 + 2 =