Skip to content

Throwaway OTel retroactive-span diagnostics spike#4685

Draft
NaluTripician wants to merge 5 commits into
Azure:mainfrom
NaluTripician:nalutripician/cosmos-otel-span-spike
Draft

Throwaway OTel retroactive-span diagnostics spike#4685
NaluTripician wants to merge 5 commits into
Azure:mainfrom
NaluTripician:nalutripician/cosmos-otel-span-spike

Conversation

@NaluTripician

Copy link
Copy Markdown
Contributor

Throwaway OpenTelemetry retroactive-span feasibility spike

Companion to the diagnostics contract doc PR (#4684). This is the isolated, throwaway spike that backs up §7–§8 of DIAGNOSTICS-CONTRACT.md — kept on its own branch so the contract doc stands alone.

What it proves

A completed DiagnosticsContext can be reconstructed into a backdated OpenTelemetry span tree using the raw opentelemetry SpanBuilder::with_start_time/with_end_time — timestamps the azure_core::tracing abstraction does not expose (it builds spans at "now"). The recorded attempts are laid on an explicit, clearly-backdated (2020) timeline with injected durations, and the operation → attempt parent/child relationships are reconstructed and asserted against an in-memory exporter.

Decoupled from the deferred #4619 capture engine

  • In-crate #[cfg(all(test, feature = "otel_spans_spike"))] module so it can use the pub(crate) DiagnosticsContextBuilder.
  • Builds the sample context from main types only — crate::diagnostics / driver::routing / options / models — with no dependency on the diagnostics::capture prototype.
  • Off by default; pulls opentelemetry + opentelemetry_sdk (in-memory exporter) only behind the otel_spans_spike feature.

Verification

  • cargo fmt ✅ · cargo clippy --all-features --all-targets -- -D warnings ✅ clean · the spike test ✅ passes.
  • Throwaway/feasibility only; no public API change; no CHANGELOG. Draft (design review).

4 files, +231: the in-crate test module, the otel_spans_spike feature + optional deps, and the module declaration.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

In-crate, off-by-default (otel_spans_spike feature) test that reconstructs a
completed DiagnosticsContext into a BACKDATED OpenTelemetry span tree via
opentelemetry SpanBuilder::with_start_time/with_end_time. Proves the OTel
mapping in DIAGNOSTICS-CONTRACT.md is feasible.

Decoupled from any capture-engine prototype: it builds the sample context
from the pub(crate) DiagnosticsContextBuilder using only crate::diagnostics
/ driver::routing / options / models types available on main - no dependency
on the deferred Azure#4619 capture module. The azure_core::tracing abstraction
builds spans at "now" and has no backdating hook, so the spike talks to the
raw opentelemetry crate + an in-memory exporter.

Throwaway/feasibility only, gated OFF by default; no public API change; no
CHANGELOG.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NaluTripician

Copy link
Copy Markdown
Contributor Author

Nice throwaway — it cleanly proves the backdated SpanBuilder::with_start_time / with_end_time path works without touching the capture engine, and the in-memory-exporter assertions on parent/child + backdated timestamps are convincing. Two things to keep in mind so this doesn't get carried into real wiring as-is:

Latent coupling to #4683's request_count() change

The test builds one span per element of ctx.requests() (the retained list) but asserts spans.len() == ctx.request_count() + 1. That holds only because compaction isn't on this branch; once #4683 lands, request_count() returns the pre-compaction total while requests() is the retained (bounded) list, so this assertion breaks. If any of this becomes production span emission, count/iterate off the same list.

Contract §7.3 gap

Same "one span per attempt" assumption — under compaction a run collapses to first+last, so a faithful emitter can't produce request_count() spans. Fine for the spike; flagging so it's not baked into the eventual emitter.

Feature / dep scoping

otel_spans_spike = ["dep:opentelemetry", "dep:opentelemetry_sdk"] enables the deps unconditionally while the only consumer is #[cfg(all(test, feature = "otel_spans_spike"))]. A non-test --all-features build pulls opentelemetry + opentelemetry_sdk (with the testing feature) into the graph with no user. Harmless for a throwaway, but worth a note if it ever graduates.

Address self-review on PR Azure#4685:

- Count/iterate attempt spans off ctx.requests() (the retained list the
  spans are actually built from) instead of ctx.request_count(). On main the
  two are equal, but once PR Azure#4683's retry-storm compaction lands
  request_count() reports the true pre-compaction total while requests() is
  the bounded first+last-per-run list, which would break the assertions. A
  faithful emitter must count off the list it emits from.
- Note (DIAGNOSTICS-CONTRACT.md 7.3) that under compaction a run collapses to
  a single span + count, so an emitter cannot produce request_count() spans.
- Document the otel_spans_spike feature dep-scoping caveat: it enables
  opentelemetry(+_sdk) unconditionally though the only consumer is the
  test-gated module, so a non-test --all-features build pulls them in unused.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NaluTripician

Copy link
Copy Markdown
Contributor Author

Addressed in 77db3bb:

  • request_count() coupling — span count/iteration is now anchored on ctx.requests() (the retained list the spans are built from); the two span-count assertions use
    equests.len() instead of ctx.request_count(), so they stay correct once Bound Cosmos diagnostics under retry storms #4683's compaction makes
    equest_count() the pre-compaction total. Added an explanatory comment at the emit site.
  • Contract §7.3 gap — added a note that under compaction a run collapses to first+last (one span + a repeat count), so a faithful emitter cannot produce
    equest_count() spans.
  • Feature/dep scoping — extended the otel_spans_spike comment in Cargo.toml to call out that the deps are enabled unconditionally while the only consumer is the test-gated module (a non-test --all-features build pulls them in unused), with a pointer to move them behind a non-test consumer / dev-dependency if this ever graduates.

cargo fmt + cargo clippy --features otel_spans_spike --all-targets clean; the spike test still passes.

NaluTripician and others added 2 commits July 6, 2026 11:17
The main merge pulled in the opentelemetry 0.32 dependency bump (Azure#4691),
which removed SpanBuilder::with_end_time. Drop the two redundant
.with_end_time(..) builder calls; the backdated end time is already
applied via end_with_timestamp(..) when each span ends, so the exported
spans and all timeline assertions are unchanged. Update the module doc
link accordingly to avoid a broken intra-doc reference.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Cosmos The azure_cosmos crate Do Not Merge

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant