OpenTelemetry
Core Principles
- Three pillars, one setup — Configure traces, metrics, and logs through a single
AddOpenTelemetry()call. UseUseOtlpExporter()for cross-cutting export to any OTLP-compatible backend. - Use
IMeterFactoryfor metrics — Never createMeterinstances withnew. The factory manages lifetime through DI and prevents leaks. - Null-safe activities —
StartActivity()returnsnullwhen no listener is attached. Always use?.when setting tags or events. - Environment variables over code — Use
OTEL_EXPORTER_OTLP_ENDPOINTandOTEL_SERVICE_NAMEso deployments control telemetry routing without code changes. - Low-cardinality metric tags — Keep metric tag combinations under ~1000 per instrument. Use span attributes or logs for high-cardinality data like user IDs or request IDs.
Patterns
Full Setup with All Three Signals
The OTLP endpoint defaults to http://localhost:4317 (gRPC). Override via:
Custom Metrics with IMeterFactory
Register a metrics class as a singleton. IMeterFactory handles Meter disposal through DI.
Multi-Dimensional Metric Tags
Three or fewer tags are allocation-free. For more, use TagList.
Custom ActivitySource for Distributed Tracing
Register the source: .AddSource("MyApp.Orders") in the tracing builder.
Aspire Dashboard for Local Development
Run the standalone Aspire Dashboard without Aspire orchestration:
Then point your app at it:
Dashboard UI is at http://localhost:18888.
Source-Generated Logging with OTel
For maximum performance, use [LoggerMessage] — eliminates boxing and allocations.
OpenTelemetry logging automatically includes TraceId and SpanId when an Activity is current.
