OpenTelemetry with Serilog and Azure Application Insights in .NET
Reading Time: 5 minutes
Problem
Most .NET teams eventually hit the same observability wall:
- Traces exist in one place, logs in another, and metrics in a third
- Log messages are inconsistent and hard to query
- Production incidents take too long to diagnose because correlation is weak
- The default logging setup is often “good enough” early on, then painful at scale
Without a clear strategy, your telemetry becomes noisy instead of actionable. You can have data and still not have answers.
Solution
Use a combined approach:
- OpenTelemetry for vendor-neutral tracing and metrics
- Serilog for structured, query-friendly logs with strong enrichment support
- Azure Application Insights as the operational backend for monitoring and investigation
This setup gives you consistent telemetry with correlation across request traces, dependencies, exceptions, and logs.
Why Serilog specifically (vs not using Serilog)?
- Better structured logging ergonomics: message templates and property capture are first-class and developer-friendly.
- Richer enrichment pipeline: add request IDs, tenant IDs, user IDs, machine info, and custom business context consistently.
- Flexible sink strategy: write to console, files, and App Insights simultaneously without custom plumbing.
- Cleaner queryability: structured fields land as searchable properties, which makes KQL queries much more effective.
- Predictable formatting and output control: useful for local debugging and production diagnostics.
Can you run OpenTelemetry + App Insights without Serilog? Yes. But teams often lose logging consistency and enrichment quality over time, which slows incident response.
Description
1) Install packages
dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.ApplicationInsights
dotnet add package Serilog.Enrichers.Environment
dotnet add package Serilog.Enrichers.Process
dotnet add package Serilog.Enrichers.Thread
2) Configure OpenTelemetry + Serilog in Program.cs
using Azure.Monitor.OpenTelemetry.AspNetCore;
using Microsoft.ApplicationInsights.Extensibility;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.UseAzureMonitor(options =>
{
options.ConnectionString = builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
});
builder.Host.UseSerilog((context, services, configuration) =>
{
var telemetryConfiguration = services.GetRequiredService<TelemetryConfiguration>();
configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.WriteTo.Console()
.WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces);
});
var app = builder.Build();
app.UseSerilogRequestLogging();
app.MapGet("/orders/{orderId}", (string orderId, ILogger<Program> logger) =>
{
logger.LogInformation("Fetching order {OrderId}", orderId);
// Simulated logic
if (orderId == "404")
{
logger.LogWarning("Order {OrderId} was not found", orderId);
return Results.NotFound();
}
return Results.Ok(new { id = orderId, status = "Processing" });
});
app.Run();
3) Add minimum config in appsettings.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId", "WithProcessId"]
}
}
Set APPLICATIONINSIGHTS_CONNECTION_STRING as an environment variable (local secrets and Azure App Service/Container App settings in production).
4) What improves in practice
- A single request can be followed across inbound HTTP, outbound dependency calls, and related logs.
- Log properties such as
OrderId,TenantId, andCorrelationIdbecome first-class query fields. - Teams can answer “what failed, where, and for whom” much faster during incidents.
5) Example Application Insights log queries
Once Serilog is sending structured logs into Application Insights, you can query them with Kusto Query Language (KQL).
Find recent warning and error logs:
traces
| where timestamp > ago(1h)
| where severityLevel >= 2
| project timestamp, severityLevel, message, operation_Id, customDimensions
| order by timestamp desc
Find all logs for a specific order ID captured by Serilog:
traces
| where timestamp > ago(24h)
| where customDimensions.OrderId == "12345"
| project timestamp, message, severityLevel, operation_Id
| order by timestamp desc
Find failed requests and join them to related logs using the operation ID:
requests
| where timestamp > ago(24h)
| where success == false
| project requestTimestamp = timestamp, name, resultCode, operation_Id, duration
| join kind=leftouter (
traces
| project traceTimestamp = timestamp, operation_Id, message, severityLevel
) on operation_Id
| order by requestTimestamp desc, traceTimestamp desc
Find exceptions together with the related structured log context:
exceptions
| where timestamp > ago(24h)
| project exceptionTimestamp = timestamp, type, outerMessage, operation_Id
| join kind=leftouter (
traces
| project traceTimestamp = timestamp, operation_Id, message, customDimensions
) on operation_Id
| order by exceptionTimestamp desc, traceTimestamp desc
Filter by a specific tenant or correlation ID when Serilog enrichers push those values:
traces
| where timestamp > ago(4h)
| where customDimensions.TenantId == "tenant-a"
or customDimensions.CorrelationId == "7f1c1a4e-8e2d-4ea7-9d95-2fd7e7c2b111"
| project timestamp, message, severityLevel, customDimensions
| order by timestamp desc
This is where Serilog pays off. Without structured properties, you are usually reduced to brittle text matching. With Serilog, fields such as OrderId, TenantId, and CorrelationId become reliable query inputs instead of substrings buried inside log messages.
Serilog vs no Serilog (quick comparison)
| Capability | OpenTelemetry + default logging only | OpenTelemetry + Serilog |
|---|---|---|
| Structured logging discipline | Possible, but often inconsistent by team | Strong and consistent via templates/enrichers |
| Context enrichment | Basic/manual | Extensive and reusable |
| Multi-destination logging | More setup effort | Straightforward with sinks |
| Local readability + production queryability | Varies by provider/config | Predictable and configurable |
| Incident debugging speed | Good baseline | Typically faster due to richer context |
Summary
OpenTelemetry gives you the telemetry standard. Azure Application Insights gives you the monitoring platform. Serilog gives you high-quality structured logs that are easier to correlate, query, and trust.
If your team is serious about reducing time-to-diagnose in production, this combination is a practical, low-friction upgrade from basic logging.