DcsvIo.D2.Telemetry
0.1.3-beta.1
dotnet add package DcsvIo.D2.Telemetry --version 0.1.3-beta.1
NuGet\Install-Package DcsvIo.D2.Telemetry -Version 0.1.3-beta.1
<PackageReference Include="DcsvIo.D2.Telemetry" Version="0.1.3-beta.1" />
<PackageVersion Include="DcsvIo.D2.Telemetry" Version="0.1.3-beta.1" />
<PackageReference Include="DcsvIo.D2.Telemetry" />
paket add DcsvIo.D2.Telemetry --version 0.1.3-beta.1
#r "nuget: DcsvIo.D2.Telemetry, 0.1.3-beta.1"
#:package DcsvIo.D2.Telemetry@0.1.3-beta.1
#addin nuget:?package=DcsvIo.D2.Telemetry&version=0.1.3-beta.1&prerelease
#tool nuget:?package=DcsvIo.D2.Telemetry&version=0.1.3-beta.1&prerelease
DcsvIo.D2.Telemetry
OpenTelemetry SDK setup (traces / metrics / logs) + OTLP exporters (per-signal opt-in via canonical env vars) + IP-restricted Prometheus scraping endpoint + aggregation of every shared lib's ActivitySource and Meter into a single AddD2Telemetry() call. Foundation lib that composition-root hosts call to wire the OTel SDK without each service duplicating ~120 lines of OpenTelemetryBuilder / ConfigureResource / per-instrumentation registration boilerplate.
The lib is intentionally independent of DcsvIo.D2.Logging. The two libs cooperate at runtime via the MEL bridge — Serilog's writeToProviders: true (set by AddD2Logging) routes through the OTLP log exporter that AddD2Telemetry registers as an ILoggerProvider — without either lib referencing the other at compile time. Hosts may wire one without the other.
The lib does NOT own:
- Serilog configuration / sinks —
DcsvIo.D2.Loggingowns them. - The
[RedactData]enforcement pipeline —DcsvIo.D2.Loggingowns it. [LoggerMessage]source-generated delegates — bootstrapping the OTel logs MEL provider during MEL bootstrap is circular.- Self-monitoring counters for export failures — the OTel SDK's own internal diagnostics suffice for the foundation tier; consumers can wire
EventListeneragainst the SDK if lib-internal export-failure visibility is needed.
Install
dotnet add package DcsvIo.D2.Telemetry
Public API surface
Composition
services.AddD2Telemetry(configuration, opts =>
{
opts.ServiceName = "edge"; // optional — defaults from OTEL_SERVICE_NAME → IHostEnvironment.ApplicationName
opts.OtlpTracesEndpoint = "https://otlp.example.com/v1/traces"; // optional — defaults from OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
opts.AdditionalActivitySources = ["YourHost.Service"]; // optional — service-specific activity sources
});
// later, inside the endpoint-routing pipeline:
app.MapD2PrometheusEndpoint();
AddD2Telemetry(IConfiguration, Action<D2TelemetryOptions>?) registers the OpenTelemetry SDK builder, the OTLP exporters whose corresponding endpoint env vars are truthy, the in-process Prometheus exporter (when EnablePrometheusExporter is true), and the standard auto-instrumentations (AspNetCore, HttpClient, GrpcNetClient, Process, Runtime). Validates options at the first IOptions<D2TelemetryOptions>.Value resolution via ValidateOnStart() — fail-fast on invalid config.
MapD2PrometheusEndpoint() maps /metrics via OpenTelemetry's MapPrometheusScrapingEndpoint, attaches an endpoint filter that returns 403 Forbidden for requests whose connection-remote IP is neither loopback nor RFC 1918 private.
OTEL_SDK_DISABLED short-circuit
When the canonical OpenTelemetry env var OTEL_SDK_DISABLED is set to "true" (case-insensitive), BOTH AddD2Telemetry and MapD2PrometheusEndpoint short-circuit:
AddD2Telemetryreturns the unmutatedservicescollection — no providers / exporters / instrumentations registered.MapD2PrometheusEndpointreturns the unmutatedendpointsbuilder — no/metricsroute mapped.
Consumers MUST NOT rely on MeterProvider / TracerProvider resolution from DI after AddD2Telemetry returns when the env var is set. Used in E2E tests where the OTel SDK is undesired (the SDK's per-process singleton state otherwise contaminates tests that build multiple hosts in sequence).
Configuration — D2TelemetryOptions
| Property | Type | Default | Notes |
|---|---|---|---|
ServiceName |
string? |
OTEL_SERVICE_NAME config → IHostEnvironment.ApplicationName |
service.name resource attribute on every span / metric / log record. Validated non-empty / non-whitespace at startup. |
OtlpTracesEndpoint |
string? |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT config |
Falsey value suppresses traces OTLP exporter; spans still emit to in-process listeners. URI-shape validated when truthy. |
OtlpMetricsEndpoint |
string? |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT config |
Falsey value suppresses metrics OTLP exporter; Prometheus scraping remains active when enabled. URI-shape validated when truthy. |
OtlpLogsEndpoint |
string? |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT config |
Falsey value suppresses logs OTLP exporter; logs still flow through other MEL providers. URI-shape validated when truthy. |
InstrumentationExcludedPaths |
IReadOnlyList<string> |
["/health", "/alive", "/metrics", "/.well-known"] |
Path prefixes excluded from AspNetCore-instrumentation auto-spans (matches DcsvIo.D2.Logging's default infrastructure-path set so spans and logs stay aligned). Validated non-empty (collection) and per-entry non-empty / non-whitespace at startup. |
AdditionalActivitySources |
IReadOnlyList<string> |
[] |
Names of additional ActivitySources registered with the tracer provider on top of the standard aggregation set. Validated per-entry non-empty when populated. |
AdditionalMeters |
IReadOnlyList<string> |
[] |
Names of additional Meters registered with the meter provider on top of the standard aggregation set. Same shape. |
EnableAspNetCoreInstrumentation |
bool |
true |
Inbound HTTP request spans + metrics. |
EnableHttpClientInstrumentation |
bool |
true |
Outbound HttpClient spans + metrics. Self-referential filter suppresses spans for outbound calls to the configured OTLP exporter endpoints (prevents infinite-loop instrumentation). |
EnableGrpcNetClientInstrumentation |
bool |
true |
Outbound gRPC client spans. |
EnableProcessInstrumentation |
bool |
true |
Process metrics (CPU, memory, fd count). |
EnableRuntimeInstrumentation |
bool |
true |
.NET runtime metrics (GC, threadpool, JIT). |
EnablePrometheusExporter |
bool |
true |
In-process Prometheus exporter; off when host wants OTLP-only metrics push. |
Constants
| Constant | Value |
|---|---|
D2TelemetryConstants.OTEL_SERVICE_NAME_CONFIG_KEY |
"OTEL_SERVICE_NAME" |
D2TelemetryConstants.OTLP_TRACES_ENDPOINT_CONFIG_KEY |
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" |
D2TelemetryConstants.OTLP_METRICS_ENDPOINT_CONFIG_KEY |
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" |
D2TelemetryConstants.OTLP_LOGS_ENDPOINT_CONFIG_KEY |
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" |
D2TelemetryConstants.OTEL_SDK_DISABLED_ENV_VAR |
"OTEL_SDK_DISABLED" |
D2TelemetryConstants.PROMETHEUS_ENDPOINT_PATH |
"/metrics" |
D2TelemetryConstants.HEALTH_ENDPOINT_PATH |
"/health" |
D2TelemetryConstants.ALIVE_ENDPOINT_PATH |
"/alive" |
D2TelemetryConstants.WELL_KNOWN_ENDPOINT_PATH |
"/.well-known" |
The OTEL_SERVICE_NAME_CONFIG_KEY value matches DcsvIo.D2.Logging's constant of the same name — both reference the OpenTelemetry-canonical env var so log + trace + metric service names stay aligned. Re-declared here rather than depending on DcsvIo.D2.Logging (the two libs are independent).
Telemetry surface coverage
AddD2Telemetry aggregates shared-lib ActivitySource / Meter wire names into the configured exporters. Package owners bind via published const string symbols (compile-time rename safety). Optional host auth modules may also emit under reserved OTel wire names registered as literals only (no ProjectReference into host packages from this lib).
Optional host auth meters: wire names
"DcsvIo.D2.Auth"/"DcsvIo.D2.Auth.Outbound"are AssemblyName / OTel series labels for hosts that ship auth runtime modules. Public Telemetry registers those names as string literals so dashboards stay continuous; it does not ProjectReference host auth packages.
ActivitySource aggregation (4 sources — tracer provider)
| Wire / owning surface | Source name | Binding in public Telemetry |
|---|---|---|
DcsvIo.D2.Handler (public) |
DcsvIo.D2.Handler |
symbol HandlerTelemetry.SourceName |
| Host auth runtime (optional) | DcsvIo.D2.Auth |
wire literal AUTH_WIRE_NAME |
| Host auth outbound (optional) | DcsvIo.D2.Auth.Outbound |
wire literal AUTH_OUTBOUND_WIRE_NAME |
DcsvIo.D2.Messaging.RabbitMq |
DcsvIo.D2.Messaging.RabbitMq |
symbol MessagingTelemetry.SOURCE_NAME |
Meter aggregation (6 meters — meter provider)
| Wire / owning surface | Meter name | Binding in public Telemetry |
|---|---|---|
DcsvIo.D2.Handler (public) |
DcsvIo.D2.Handler |
symbol HandlerTelemetry.SourceName |
| Host auth runtime (optional) | DcsvIo.D2.Auth |
wire literal AUTH_WIRE_NAME |
| Host auth outbound (optional) | DcsvIo.D2.Auth.Outbound |
wire literal AUTH_OUTBOUND_WIRE_NAME |
DcsvIo.D2.Messaging.RabbitMq (public) |
DcsvIo.D2.Messaging.RabbitMq |
symbol MessagingTelemetry.SOURCE_NAME |
DcsvIo.D2.Caching.Distributed.Redis |
DcsvIo.D2.Caching.Distributed.Redis |
symbol RedisCacheTelemetry.METER_NAME |
DcsvIo.D2.Caching.Local |
DcsvIo.D2.Caching.Local |
symbol LocalCacheTelemetry.METER_NAME |
The two cache libs publish counters only — no spans — so they appear in the meter list but not the activity-source list. ProjectReferenced libs use source-of-truth const string symbols so a rename surfaces as a build break. Optional host auth names are literals. Spec-pinning unit tests in AggregatedTelemetrySourcesTests and D2TelemetryConstantsTests pin the literal wire values so an in-place value change surfaces as a test failure — operators querying Tempo / Prometheus by literal source name (DcsvIo.D2.Auth) are protected from silent wire-format drift.
Auto-instrumentations
| Instrumentation | Default | Notes |
|---|---|---|
| AspNetCore inbound | enabled | Filter callback excludes /health, /alive, /metrics, /.well-known (configurable via InstrumentationExcludedPaths). RecordException = true captures exception type + stack trace as span events (no PII — diagnostic-class data). |
| HttpClient outbound | enabled | FilterHttpRequestMessage callback suppresses spans for outbound calls whose URI starts with any configured OTLP endpoint (prevents infinite-loop instrumentation). EnrichWithHttpRequestMessage re-sets url.full to scheme + host + port + path (no query string) as defense-in-depth against future SDK regressions on query-string PII. |
| GrpcNetClient outbound | enabled | Standard span tags. |
| Process metrics | enabled | CPU, memory, fd count. |
| Runtime metrics | enabled | GC, threadpool, JIT. |
| Prometheus exporter | enabled | In-process scraping endpoint mapped via MapD2PrometheusEndpoint. |
Dependencies
| Package | Why |
|---|---|
OpenTelemetry.Api |
Pinned at 1.15.3 — surgical override per GHSA-g94r-2vxg-569j on the 1.15.0 line. Direct ref required to override the transitive 1.15.0 pulled by Instrumentation.* packages. |
OpenTelemetry |
Core SDK (OpenTelemetryBuilder, BatchExportProcessor, etc.). |
OpenTelemetry.Extensions.Hosting |
services.AddOpenTelemetry() extension + WithTracing / WithMetrics overloads. |
OpenTelemetry.Exporter.OpenTelemetryProtocol |
Pinned at 1.15.3 — surgical override per GHSA-mr8r-92fq-pj8p + GHSA-q834-8qmm-v933. OTLP HTTP/Protobuf exporter for traces / metrics / logs. |
OpenTelemetry.Exporter.Prometheus.AspNetCore |
Pinned at 1.15.0-beta.1 — long-running beta in OTel .NET; >18mo of v1 production stability supports the pin. Required for MapPrometheusScrapingEndpoint. |
OpenTelemetry.Instrumentation.AspNetCore |
Inbound HTTP request spans + metrics. |
OpenTelemetry.Instrumentation.GrpcNetClient |
Pinned at 1.15.0-beta.1. Outbound gRPC client spans. |
OpenTelemetry.Instrumentation.Http |
Outbound HttpClient spans + metrics. |
OpenTelemetry.Instrumentation.Process |
Pinned at 1.15.0-beta.1. Process metrics. |
OpenTelemetry.Instrumentation.Runtime |
.NET runtime metrics. |
JetBrains.Annotations |
[MustDisposeResource] annotations on disposable factory paths (none currently; consumed transitively). |
| PackageId | Why |
|---|---|
DcsvIo.D2.Utilities |
Falsey() / Truthy() / ToNullIfEmpty() extensions for options validation + env-var resolution. |
DcsvIo.D2.AspNetCore |
Canonical InfrastructurePathMatcher (consumed by the AspNetCore-instrumentation Filter callback) + D2AspNetCoreConstants (re-exported HEALTH / ALIVE / METRICS / WELL_KNOWN endpoint path constants + DEFAULT_INFRASTRUCTURE_PATHS). Single source of truth shared with DcsvIo.D2.Logging's request-logging middleware. |
DcsvIo.D2.Handler |
HandlerTelemetry.SourceName const referenced by the aggregation table. |
DcsvIo.D2.Messaging.RabbitMq |
MessagingTelemetry.SOURCE_NAME const. |
DcsvIo.D2.Caching.Distributed.Redis |
RedisCacheTelemetry.METER_NAME const. |
DcsvIo.D2.Caching.Local.Default |
LocalCacheTelemetry.METER_NAME const. |
Not referenced (wire literals only): optional host auth runtime + outbound modules. Public Telemetry registers OTel wire names "DcsvIo.D2.Auth" / "DcsvIo.D2.Auth.Outbound" as string literals — see AggregatedTelemetrySources.AUTH_WIRE_NAME / AUTH_OUTBOUND_WIRE_NAME.
Edge cases / gotchas
- OTel SDK builds singleton
MeterProvider/TracerProviderper process. Tests that build multiple hosts in sequence see SDK static state contamination across host lifetimes. The lib's integration tests pin the contract via[Collection("OtelStaticState")]to serialize against any other test that touches the SDK. - Infrastructure-path matching lives in
DcsvIo.D2.AspNetCore. The AspNetCore-instrumentationFiltercallback consumes the publicInfrastructurePathMatcher;DcsvIo.D2.Logging's request-logging middleware consumes the same one. The path set (/health,/alive,/metrics,/.well-known) stays aligned across the two consumers without per-lib literal duplication. - Cache libs (
Redis,Local) expose only theirMeter— no spans. The libs publish aggregate counters (hits, misses, sets, evictions) without per-call spans because cache work is sub-microsecond and per-call instrumentation would dominate it. The aggregation table reflects this — meter list includes both cache libs; activity-source list does not. MessagingTelemetry,RedisCacheTelemetry, andLocalCacheTelemetryarepublic staticso theirSOURCE_NAMEconsts are reachable cross-assembly. The consts themselves arepublic; the surrounding class visibility matches so cross-assembly references compile.- Per-signal OTLP exporter is registered ONLY when its endpoint env var is truthy. When all three are absent, the SDK still builds — spans emit to in-process listeners; metrics flow to the Prometheus endpoint when enabled; logs flow through other MEL providers. This is the fail-soft behavior: production sets all three; dev / test omits any combination cleanly.
- HttpClient instrumentation's self-referential filter prevents infinite-loop instrumentation. Outbound calls whose URI starts with any configured OTLP endpoint are suppressed from span emission (else
HttpClient → OTLP → HttpClient → OTLPloops forever). url.fullquery-string stripping is defense-in-depth. The OTel 1.15.x HttpClient instrumentation default already strips query strings from the standard span tags, but theEnrichWithHttpRequestMessagecallback explicitly re-setsurl.fullto scheme + host + port + path so a future SDK regression doesn't silently leak query-string PII.- OTLP collector unavailable → SDK retries internally + drops on full buffer. The OTel SDK's
BatchExportProcessorowns retry / drop semantics; the lib does not add another layer. Operators notice unavailability via missing data in dashboards (the SDK's own internal diagnostics +EventListeneragainstOpenTelemetry.EventSourceprovide drop-rate visibility for operators who wire it).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- DcsvIo.D2.AspNetCore (>= 0.1.2)
- DcsvIo.D2.Caching.Distributed.Redis (>= 0.1.2)
- DcsvIo.D2.Caching.Local.Default (>= 0.1.1)
- DcsvIo.D2.Handler (>= 0.1.2)
- DcsvIo.D2.Messaging.RabbitMq (>= 0.1.2)
- DcsvIo.D2.Utilities (>= 0.1.1)
- dotenv.net (>= 4.0.2)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.EntityFrameworkCore (>= 10.0.7)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.7)
- Microsoft.IdentityModel.Tokens (>= 8.16.0)
- NodaTime (>= 3.2.2)
- Npgsql (>= 10.0.2)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.1)
- Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime (>= 10.0.1)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.15.3)
- OpenTelemetry.Exporter.Prometheus.AspNetCore (>= 1.15.0-beta.1)
- OpenTelemetry.Extensions.Hosting (>= 1.15.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.15.0)
- OpenTelemetry.Instrumentation.GrpcNetClient (>= 1.15.0-beta.1)
- OpenTelemetry.Instrumentation.Http (>= 1.15.0)
- OpenTelemetry.Instrumentation.Process (>= 1.15.0-beta.1)
- OpenTelemetry.Instrumentation.Runtime (>= 1.15.0)
- RabbitMQ.Client (>= 7.2.1)
- Serilog.AspNetCore (>= 10.0.0)
- Serilog.Formatting.Compact (>= 3.0.0)
- StackExchange.Redis (>= 2.8.31)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.3-beta.1 | 60 | 7/17/2026 |
| 0.1.2-beta.1 | 55 | 7/17/2026 |