OtelHelper — OpenTelemetry Helper for .NET¶
Observability library for .NET 8+ applications. Abstracts OpenTelemetry SDK configuration to provide standardized instrumentation.
📖 HOW-TO.md — Developer guide (logs, traces, metrics, examples) 🧪 TESTS.md — Full list of unit tests 🚀 example/README.md — Sample apps with test endpoints and Grafana queries
Quick Start¶
Optionally, with overrides:
After registration, available via DI:
- ActivitySource — for creating manual spans
- Meter — for creating custom metrics
- ActivitySourceExtensions.StartRootActivity() — for independent traces in workers
Environment Variables¶
Required (injected automatically by infrastructure)¶
| Variable | Source | Description | Default |
|---|---|---|---|
SERVICE_NAME |
CI/CD Pipeline | Service name | my-service |
ENVIRONMENT |
Helm Chart (values.yaml) |
Environment: LOCAL, DEV, HML, PRD. Unrecognized value → LOCAL. |
LOCAL |
OTEL_EXPORTER_OTLP_ENDPOINT |
Helm Chart | Collector base host | http://localhost |
These variables are injected automatically. Application teams do not need to configure them manually.
Optional¶
| Variable | Description | Default |
|---|---|---|
OTEL_HELPER_DEBUG_LEVEL |
Debug mode: forces Debug log level, all instrumentations, attribute debug=true (true/false) |
false |
OTEL_HELPER_METRICS_PORT |
Prometheus /metrics port when no OTLP endpoint is configured |
9464 |
Extra Instrumentation¶
| Variable | Description | Default |
|---|---|---|
OTEL_HELPER_EXTRA_INSTRUMENTATION |
Extra instrumentations: SQL, AWS. Deprecated — use opt-in subpackages instead. |
SQL |
OTEL_HELPER_SAMPLE_RATIO |
Head sampling ratio (0.0-1.0). 1.0 = AlwaysOn. | 1.0 |
Debug mode (
OTEL_HELPER_DEBUG_LEVEL=true) enables all extra instrumentations automatically.⚠️
OTEL_HELPER_DEBUG_LEVEL=truein production causes cost explosion and backend saturation. Use only for targeted troubleshooting.
Derived Ports¶
| Signal | Port | Resulting Endpoint |
|---|---|---|
| OTLP (traces/metrics/logs) | :4317 |
{OTEL_EXPORTER_OTLP_ENDPOINT}:4317 |
TLS / Transport Security¶
Transport is determined by the endpoint scheme:
| Endpoint | Transport |
|---|---|
https://host:4317 |
TLS (system CA trust store) |
http://host:4317 |
Plaintext (insecure) |
host:4317 (no scheme) |
TLS (secure by default) |
Override with OTEL_EXPORTER_OTLP_INSECURE: true forces plaintext, false forces TLS. Code config (TelemetryOptions.OtelCollectorEndpoint scheme) always wins over env var.
For a local plaintext collector, use http:// in the endpoint or set OTEL_EXPORTER_OTLP_INSECURE=true.
Behavior when no OTLP endpoint is configured¶
When OTEL_EXPORTER_OTLP_ENDPOINT is not set, the library automatically falls back to:
| Signal | Behavior |
|---|---|
| Metrics | Exposed via Prometheus HTTP /metrics on port 9464 |
| Traces | In-process only (context propagation works, no export) |
| Logs | stdout/console only (no OTel export) |
The Prometheus metrics port is configurable via OTEL_HELPER_METRICS_PORT env var (default: 9464).
This enables the standard Kubernetes pattern: deploy without a collector, and let Prometheus/VictoriaMetrics scrape /metrics directly from the pod.
Standard OpenTelemetry SDK Variables¶
Recognized natively by the SDK. The lib does not override them — if defined, the SDK respects them.
Sampling¶
| Variable | Description | Default |
|---|---|---|
OTEL_TRACES_SAMPLER |
SDK sampler. | parentbased_always_on |
OTEL_TRACES_SAMPLER_ARG |
Sampler argument. | empty |
Context Propagation¶
| Variable | Description | Default |
|---|---|---|
OTEL_PROPAGATORS |
W3C propagators. | tracecontext,baggage |
OTLP Exporter¶
| Variable | Description | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_PROTOCOL |
OTLP protocol. | grpc |
OTEL_EXPORTER_OTLP_INSECURE |
TLS override: true = plaintext, false = TLS. Unset = derived from endpoint scheme (secure by default). |
(unset) |
OTEL_EXPORTER_OTLP_HEADERS |
Additional headers (e.g., auth). | empty |
OTEL_EXPORTER_OTLP_TIMEOUT |
Timeout in ms. | 10000 |
Behavior per Environment¶
| Environment | Trace Sampling | Log Level |
|---|---|---|
LOCAL |
100% (AlwaysOn) | Debug |
DEV |
100% (AlwaysOn) | Information |
HML |
100% (AlwaysOn) | Information |
PRD |
100% (AlwaysOn) | Warning |
The SDK sends 100% of traces to the Collector in all environments. Tail-based sampling is the Collector's responsibility (Agent → Gateway), which decides what to keep based on errors, latency, and configured rate per environment.
What is Instrumented Automatically¶
Traces¶
| Instrumentation | What it captures |
|---|---|
| ASP.NET Core | Incoming HTTP requests (server spans) |
| HttpClient | Outgoing HTTP requests (client spans) |
| gRPC Client | Outgoing gRPC calls |
| SqlClient | SQL queries — requires SQL in OTEL_HELPER_EXTRA_INSTRUMENTATION (enabled by default) |
| AWS SDK | S3, SQS, DynamoDB calls, etc. — requires AWS in OTEL_HELPER_EXTRA_INSTRUMENTATION |
| Custom Sources | Spans created via ActivitySource(serviceName) |
Sampler: AlwaysOnSampler in all environments (configurable via opts.Sampler). Tail-based sampling is done in the Collector.
Paths
/ping,/health,/healthz,/readyare filtered automatically — they do not generate trace spans.
Metrics¶
| Instrumentation | What it captures |
|---|---|
| .NET Runtime | GC, thread pool, JIT, assemblies |
| ASP.NET Core | Request duration, active requests, request size |
| HttpClient | Outbound request duration, active requests |
| Custom Meters | Business metrics via Meter(serviceName) |
Metrics are exported every 30 seconds (overrides SDK default of 60s).
Exemplars enabled (ExemplarFilterType.TraceBased) — metric → trace correlation in Grafana.
Logs¶
| Feature | Description |
|---|---|
| OTLP Export | Logs sent via OTLP to the Collector |
| Trace Correlation | traceId/spanId automatic via Activity.Current |
| Formatted Message | Formatted message included |
| Scopes | ILogger scopes included |
The lib only adds the OTLP exporter to the logging pipeline. Console logging and filters are the application's responsibility.
Library Options (TelemetryOptions)¶
| Property | Type | Env Var | Default |
|---|---|---|---|
ServiceName |
string | SERVICE_NAME / OTEL_SERVICE_NAME |
my-service |
Environment |
DeploymentEnvironment | ENVIRONMENT |
LOCAL |
OtelCollectorEndpoint |
string | OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4317 |
DebugLevel |
bool | OTEL_HELPER_DEBUG_LEVEL |
false |
ExportTimeoutMs |
int | — | 10000 (10s) |
ExtraInstrumentation |
string | OTEL_HELPER_EXTRA_INSTRUMENTATION |
SQL |
Sampler |
Sampler | — | AlwaysOnSampler |
MinimumLogLevel |
LogLevel? | — | null (auto per environment) |
ResourceAttributes |
Dictionary |
— | empty |
AdditionalActivitySources |
List\<string> | — | empty |
ServiceNameresolves:SERVICE_NAME>OTEL_SERVICE_NAME>"my-service".MinimumLogLevelwhen null: LOCAL=Debug, DEV/HML=Information, PRD=Warning. UseGetDefaultLogLevel()to query.Samplerdefault is AlwaysOnSampler — override only if you have a specific reason. Env vars are applied viaIPostConfigureOptions— consumer overrides take priority.
Opt-in Subpackages¶
AWS, Redis, SQL, and Profiling instrumentations are no longer bundled in core. Install the subpackage you need:
| Package | Registration | What it instruments |
|---|---|---|
OtelHelper.AWS |
services.AddOtelHelperAws() |
AWS SDK calls (S3, SQS, DynamoDB, etc.) |
OtelHelper.Redis |
services.AddOtelHelperRedis() |
StackExchange.Redis commands |
OtelHelper.Sql |
services.AddOtelHelperSql() |
SqlClient queries |
OtelHelper.Profiling |
services.AddOtelHelperProfiling() |
Pyroscope continuous profiling |
Usage¶
services.AddOtelHelper();
// Add only the instrumentations you need:
services.AddOtelHelperAws();
services.AddOtelHelperRedis();
services.AddOtelHelperSql();
Redis with explicit connection:
Profiling (Pyroscope)¶
Requires these environment variables set on the container:
env:
- name: CORECLR_ENABLE_PROFILING
value: "1"
- name: CORECLR_PROFILER
value: "{BD1A650D-AC5D-4896-B64F-D6FA25D6B26A}"
- name: CORECLR_PROFILER_PATH
value: "/opt/pyroscope/Pyroscope.Profiler.Native.so"
- name: LD_PRELOAD
value: "/opt/pyroscope/Pyroscope.Linux.ApiWrapper.x64.so"
Note:
OTEL_HELPER_EXTRA_INSTRUMENTATIONenv var still works for backward compatibility but subpackages are the recommended approach.
Architecture¶
Application (.NET SDK) → OTLP gRPC :4317 → Agent Collector → Gateway Collector → Tempo / VictoriaMetrics / Loki
All telemetry goes through the Collector. The SDK does NOT export directly to backends.
Tests¶
Run unit tests¶
docker run --rm -v "$(pwd):/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 dotnet test OtelHelper.Tests
Coverage: see TESTS.md for the full list.
Examples¶
See example/README.md for sample apps with distributed traces.
Supported Targets¶
- .NET 8 (LTS)
- .NET 10 (LTS) — via roll-forward
.NET 10 Note¶
The lib is compiled for net8.0 and runs on .NET 10 via roll-forward. Requirements:
- Runtime image: use
aspnet(notruntime) — the lib depends onMicrosoft.AspNetCore.Appfor ASP.NET Core instrumentation - Roll-forward: add
DOTNET_ROLL_FORWARD=Majorenv var if using a custom image:
Project Structure¶
OtelHelper.sln
├── OtelHelper/ # Main library
│ ├── TelemetryExtensions.cs # Entry point AddOtelHelper()
│ ├── ActivitySourceExtensions.cs # StartRootActivity() for workers
│ ├── TracerSetup.cs # Tracing + health filter + sampler
│ ├── Tracing/
│ │ └── DebugTraceStateProcessor.cs # Injects tracestate debug=true for tail sampling
│ ├── MetricsSetup.cs # Metrics with exemplars
│ ├── LoggingSetup.cs # Logging via OpenTelemetry SDK
│ ├── Models/
│ │ ├── DeploymentEnvironment.cs # Environment enum
│ │ ├── TelemetryOptions.cs # Configuration POCO
│ │ ├── TelemetryOptionsPostConfigure.cs # Env var resolution
│ │ └── TelemetryOptionsValidator.cs # Startup validation
├── OtelHelper.Tests/ # Unit tests (xUnit)
└── example/ # Sample apps
├── dotnet-api/ # API frontend
├── dotnet-backend/ # Backend with DB + external call
└── dotnet-process/ # Background worker