dotnet-observability
dotnet-observability
Modern observability for .NET applications using OpenTelemetry, structured logging, health checks, and custom metrics. Covers the three pillars of observability (traces, metrics, logs), integration with Microsoft.Extensions.Diagnostics and System.Diagnostics, and production-ready health check patterns.
Scope
- OpenTelemetry traces, metrics, and logs setup
- Health check endpoints and liveness/readiness patterns
- Custom metrics with System.Diagnostics.Metrics
- Structured logging integration with OTel export
Out of scope
- DI container mechanics and service lifetimes -- see [skill:dotnet-csharp-dependency-injection]
- Async/await patterns -- see [skill:dotnet-csharp-async-patterns]
- Testing observability output -- see [skill:dotnet-integration-testing]
- Middleware pipeline patterns (request logging, exception handling) -- see [skill:dotnet-middleware-patterns]
Cross-references: [skill:dotnet-csharp-dependency-injection] for service registration, [skill:dotnet-csharp-async-patterns] for async patterns in background exporters, [skill:dotnet-resilience] for Polly telemetry integration, [skill:dotnet-middleware-patterns] for request/exception logging middleware.
OpenTelemetry Setup
OpenTelemetry is the standard observability framework in .NET. The .NET SDK includes native support for System.Diagnostics.Activity (traces) and System.Diagnostics.Metrics (metrics), which OpenTelemetry collects and exports.
Package Landscape
| Package | Purpose |
|---|---|
OpenTelemetry.Extensions.Hosting |
Host integration, lifecycle management |
OpenTelemetry.Instrumentation.AspNetCore |
Automatic HTTP server trace/metric instrumentation |
OpenTelemetry.Instrumentation.Http |
Automatic HttpClient trace/metric instrumentation |
OpenTelemetry.Instrumentation.Runtime |
GC, thread pool, assembly metrics |
OpenTelemetry.Exporter.OpenTelemetryProtocol |
OTLP exporter (gRPC/HTTP) for collectors |
OpenTelemetry.Exporter.Console |
Console exporter for local development |
Install the core stack:
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.*" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.*" />
Aspire Service Defaults Integration
If using .NET Aspire, the ServiceDefaults project configures OpenTelemetry automatically. This is the recommended approach for Aspire apps -- do not duplicate this configuration manually:
// ServiceDefaults/Extensions.cs (generated by Aspire)
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
// ... other defaults
return builder;
}
For non-Aspire apps, configure OpenTelemetry explicitly as shown below.
Full Configuration (Non-Aspire)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(
serviceName: builder.Environment.ApplicationName,
serviceVersion: typeof(Program).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ?? "unknown"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MyApp.*") // Custom ActivitySources
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.*") // Custom Meters
.AddOtlpExporter());
OTLP Configuration via Environment Variables
The OTLP exporter reads standard environment variables -- no code changes needed between environments:
# Collector endpoint (gRPC default)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# Or HTTP/protobuf
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Resource attributes
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=myapp
# Service name (overrides code-based configuration)
OTEL_SERVICE_NAME=order-api
For detailed code examples (custom traces, metrics, structured logging, health checks, production configuration), see examples.md in this skill directory.
Key Principles
- Use OpenTelemetry as the standard -- it provides vendor-neutral instrumentation that works with any backend (Prometheus, Grafana, Datadog, Azure Monitor, AWS X-Ray)
- Use
IMeterFactoryfrom DI -- do not createMeterinstances directly; the factory integrates with the DI lifecycle and OpenTelemetry registration - Use source-generated
LoggerMessagefor hot paths -- zero allocation when the log level is disabled - Separate liveness from readiness -- liveness checks should not include dependency health; readiness checks should
- Configure via environment variables -- OTLP endpoint, service name, and resource attributes should not be hardcoded
- Enrich logs with trace context -- structured logging with
TraceIdandSpanIdenables log-to-trace correlation - Follow OpenTelemetry semantic conventions for metric and span names
Agent Gotchas
- Do not create
MeterorActivitySourcevianewin DI-registered services without usingIMeterFactory-- instruments created outside the factory are not collected by the OpenTelemetry SDK. UseIMeterFactory.Create()forMeterinstances.ActivitySourceis static and registered via.AddSource(). - Do not add dependency checks to liveness endpoints -- a database outage should not restart the app. Only the readiness endpoint should check dependencies.
- Do not use
ILogger.LogInformation("message: " + value)or string interpolation$"message: {value}"-- use structured logging templates:ILogger.LogInformation("message: {Value}", value). String concatenation and interpolation bypass structured logging and prevent log indexing. - Do not configure OTLP endpoints in code for production -- use environment variables (
OTEL_EXPORTER_OTLP_ENDPOINT) so the same image works across environments. - Do not forget to register custom
ActivitySourcenames with.AddSource("MyApp.*")-- unregistered sources are silently ignored and produce no traces.
References
- OpenTelemetry .NET documentation
- .NET observability with OpenTelemetry
- ASP.NET Core health checks
- System.Diagnostics.Metrics
- High-performance logging in .NET
- Logging in .NET: Log filtering
- Serilog OpenTelemetry sink
Attribution
Adapted from Aaronontheweb/dotnet-skills (MIT license).
More from novotnyllc/dotnet-artisan
dotnet-csharp
Baseline C# skill loaded for every .NET code path. Guides language patterns (records, pattern matching, primary constructors, C# 8-15), coding standards, async/await, DI, LINQ, serialization, domain modeling, concurrency, Roslyn analyzers, globalization, native interop (P/Invoke, LibraryImport, ComWrappers), WASM interop (JSImport/JSExport), and type design. Spans 25 topics. Do not use for ASP.NET endpoint architecture, UI framework patterns, or CI/CD guidance.
129dotnet-ui
Builds .NET UI apps across Blazor (Server, WASM, Hybrid, Auto), MAUI (XAML, MVVM, Shell, Native AOT), Uno Platform (MVUX, Extensions, Toolkit), WPF (.NET 8+, Fluent theme), WinUI 3 (Windows App SDK, MSIX, Mica/Acrylic, adaptive layout), and WinForms (high-DPI, dark mode) with JS interop, accessibility (SemanticProperties, ARIA), localization (.resx, RTL), platform bindings (Java.Interop, ObjCRuntime), and framework selection. Spans 20 topic areas. Do not use for backend API design or CI/CD pipelines.
99dotnet-api
Builds ASP.NET Core APIs, EF Core data access, gRPC, SignalR, and backend services with middleware, security (OAuth, JWT, OWASP), resilience, messaging, OpenAPI, .NET Aspire, Semantic Kernel, HybridCache, YARP reverse proxy, output caching, Office documents (Excel, Word, PowerPoint), PDF, and architecture patterns. Spans 32 topic areas. Do not use for UI rendering patterns or CI/CD pipeline authoring.
90dotnet-testing
Defines .NET test strategy and implementation patterns across xUnit v3 (Facts, Theories, fixtures, IAsyncLifetime), integration testing (WebApplicationFactory, Testcontainers), Aspire testing (DistributedApplicationTestingBuilder), snapshot testing (Verify, scrubbing), Playwright E2E browser automation, BenchmarkDotNet microbenchmarks, code coverage (Coverlet), mutation testing (Stryker.NET), UI testing (page objects, selectors), and AOT WASM test compilation. Spans 13 topic areas. Do not use for production API architecture or CI workflow authoring.
86dotnet-advisor
Routes .NET/C# requests to the correct domain skill and loads coding standards as baseline for all code paths. Determines whether the task needs API, UI, testing, devops, tooling, or debugging guidance based on prompt analysis and project signals, then invokes skills in the right order. Always invoked after [skill:using-dotnet] detects .NET intent. Do not use for deep API, UI, testing, devops, tooling, or debugging implementation guidance.
60dotnet-debugging
Debugs Windows and Linux/macOS applications (native, .NET/CLR, mixed-mode) with WinDbg MCP (crash dumps, !analyze, !syncblk, !dlk, !runaway, !dumpheap, !gcroot, BSOD), dotnet-dump, lldb with SOS, createdump, and container diagnostics (Docker, Kubernetes). Hang/deadlock diagnosis, high CPU triage, memory leak investigation, kernel debugging, and dotnet-monitor for production. Spans 17 topic areas. Do not use for routine .NET SDK profiling, benchmark design, or CI test debugging.
57