Skip to content

v7: OpenTelemetry tracing and metrics, Debug logging - #115

Merged
christopherread merged 1 commit into
v7from
v7-telemetry
Oct 25, 2026
Merged

christopherread merged 1 commit into
v7from
v7-telemetry

Conversation

@christopherread

Copy link
Copy Markdown
Owner

Fifth v7 increment, part of #90 (design, section 7). Targets v7.

Use

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing.AddSource(ObvsTelemetry.ActivitySourceName))   // "Obvs"
    .WithMetrics(metrics => metrics.AddMeter(ObvsTelemetry.MeterName));           // "Obvs"

Obvs doesn't depend on OpenTelemetry. It uses .NET's built-in ActivitySource and Meter, plus System.Diagnostics.DiagnosticSource 10.0.13 on net472 and netstandard2.0.

What's emitted

Names follow OpenTelemetry's messaging conventions, with the service as the destination.

Name Details
Span (Producer) send / publish / request / reply <service> messaging.system=obvs, messaging.operation.name, messaging.destination.name, obvs.message.type
Span (Consumer) process <service> one per handler run; on failure: Error status, error.type and an exception event
Counter messaging.client.sent.messages by operation, service and message type
Histogram (s) messaging.process.duration per handler run, with error.type when it failed
Log (Debug) Obvs send PlaceOrder on service 'orders'. category Obvs.Hosting.Messages; handler errors stay at Error
  • Child spans: messages a handler sends are children of its process span, because the span is current while the handler runs.
  • Where it hooks in: wrappers around each service's endpoints, which know the service name (the same approach as v6's logging wrappers), plus the handler runner from v7: Handlers, RequestAsync and MaxConcurrency #114.

Not in this increment

Tests (written first)

  • 9 new tests in Obvs.Tests/Hosting/Telemetry/, run on the in-memory transport. Each uses a unique service name, because trace and metric listeners are process-wide and tests run in parallel. All 9 failed first, because no span, measurement or log appeared.
  • Does the log-level test catch a real bug? I changed the Debug log to Information on purpose, and ShouldLogSentMessagesAtDebugLevelOnly failed ("Expected: Debug, Actual: Information").
  • Repeat runs: the whole core project, all 106 tests, passed 10 runs in a row.
  • Whole solution: a clean build has 0 warnings across net472, netstandard2.0 and net10.0, and all 198 unit tests pass.

🤖 Generated with Claude Code

…ages

Obvs now traces under the ActivitySource and measures under the Meter
named in ObvsTelemetry ("Obvs"), following OpenTelemetry's messaging
conventions with the service as the destination:
- send/publish/request/reply <service> Producer spans for outgoing
  messages, and a process <service> Consumer span for each handler run,
  with messaging.system=obvs and obvs.message.type; a failing handler's
  span records the error and exception, and messages a handler sends are
  child spans of it.
- messaging.client.sent.messages counts outgoing messages by operation,
  and messaging.process.duration measures handler runs, with error.type
  when they fail.

Outgoing messages are logged at Debug through ILogger. The telemetry
wraps each service's endpoints, which know the service name, and the
handler runner. Trace context across processes needs message headers in
every transport, and comes next.

Part of #90.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@christopherread christopherread added this to the v7 milestone Oct 25, 2026
@christopherread
christopherread merged commit 4da4be6 into v7 Oct 25, 2026
1 check passed
@christopherread
christopherread deleted the v7-telemetry branch October 25, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant