Skip to content

feat(tracing): support Dynatrace OneAgent in-process trace capture - #512

Merged
sjvans merged 3 commits into
developfrom
feat/oneagent-inprocess-trace-capture
Sep 18, 2026
Merged

sjvans merged 3 commits into
developfrom
feat/oneagent-inprocess-trace-capture

Conversation

@sjvans

@sjvans sjvans commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #509. Follow-up to #502 / #504.

What this changes

On the OneAgent path — Dynatrace OneAgent present (DT_NODE_PRELOAD_OPTIONS set), kind *-to-dynatrace, and no @opentelemetry/exporter-trace-otlp-proto installed — the tracing factory now registers a recording NodeTracerProvider with an empty span-processor list instead of warning and bailing out.

The provider records spans but exports nothing itself, so the CDS spans actually come into existence for OneAgent's in-process OpenTelemetry capture to pick up — without us exporting (and thus duplicating) them.

The factory's branching was refactored around two orthogonal axes — via_one_agent (OneAgent present, no exporter) and resource (truthy = standalone, falsy = CALM under @sap/xotel-agent-ext-js):

standalone (resource) CALM (no resource)
not OneAgent register provider with the configured exporter's processor delegate the processor to @sap/xotel-agent-ext-js
OneAgent register recording provider, no processor (in-process capture) rejected — throw

The CALM + OneAgent combination is now rejected fail-fast: two agents owning tracing at once is a contradictory, unverified setup, so the factory throws rather than silently doing nothing.

This is the plugin-side prerequisite for OneAgent in-process capture. It is correct and necessary — but, as the analysis below shows, it is not sufficient on its own with current OneAgent versions.

Analysis / findings

I live-verified this against a OneAgent-injected app on SAP BTP CF (classic-managed Dynatrace tenant, OneAgent 1.339). Findings:

OneAgent's in-process OTel capture is version-sensitive to the OpenTelemetry JS SDK layout. OneAgent instruments OTel-JS by wrapping getTracer on the class defined in @opentelemetry/sdk-trace-base's BasicTracerProvider.js, then applying its span transformer to the returned tracer. (Its other, deeper Tracer/Span patch is version-gated to SDK ^1.0.0 and is skipped on 2.x.)

OpenTelemetry JS SDK sdk-trace-base 2.9.0 restructured the packages: getTracer moved out of sdk-trace-base into a new package @opentelemetry/sdk-trace (TracerProvider.js), and BasicTracerProvider.js became a thin re-export BasicTracerProvider-shim.js. On ≥2.9 there is no BasicTracerProvider.js for OneAgent to match, and it has no rule for the new @opentelemetry/sdk-trace package — so the getTracer wrap never attaches and the CDS INTERNAL spans are never captured.

Exact, empirically confirmed boundary:

@opentelemetry/sdk-trace-base Provider layout OneAgent 1.339 in-process capture
≤ 2.8.0 real BasicTracerProvider.js with getTracer ✅ captures the full CDS span tree
≥ 2.9.0 BasicTracerProvider-shim.js + split into @opentelemetry/sdk-trace ❌ only the HTTP entry point (OneAgent's native sensor)

Clean A/B on the same app + same OneAgent:

  • With this fix and the plugin's declared @opentelemetry/sdk-trace-* ^2.8 floating to 2.11.0 (post-split): only the HTTP PurePath appears — no CDS spans.
  • Pinning the trace SDK to 2.8.0 (last pre-split): the full CDS tree nests under the HTTP request — CatalogService - tx → CatalogService - READ … → db - READ … → @cap-js/sqlite - prepare/stmt.all SELECT … (scope @cap-js/telemetry, SpanKind.INTERNAL).

The HTTP entry point is captured in both cases because that is OneAgent's own HTTP sensor, not OTel.

Conclusion. This is an OneAgent-side gap: OneAgent has not yet been updated for the OTel-JS SDK 2.9 package restructure. The full fix is on the Dynatrace side (OneAgent needs to instrument the current layout — @opentelemetry/sdk-trace's TracerProvider.js / Tracer.js / Span.js, or relax the deep-patch version gate to ^2.0.0). A support ticket with Dynatrace references this PR.

Dependencies are kept at ^2.8 (floating to 2.x latest) — pinning the plugin to the last pre-split patch to work around a OneAgent bug would freeze the OTel SDK and is not a maintainable trade-off. Once OneAgent adds support for the current layout, in-process capture works with no further change here.

Docs

  • README (Dynatrace OneAgent section): documents in-process capture as the supported path — no exporter needed when OneAgent is present, or install the OTLP exporter to have the plugin export the spans itself (which takes precedence).
  • CHANGELOG: unreleased 2.1.0 entry describes the recording-provider behavior.

The temporary pin that works around the current OneAgent gap is posted as a workaround comment on #509 rather than baked into the README, so it can simply be dropped once OneAgent catches up.

Tests

test/tracing-one-agent.test.js asserts, without a live tenant:

  • the illustrative crash mode — a provider whose only processor is undefined throws on the first span (why the OneAgent path uses [], not [processor]);
  • @opentelemetry/exporter-trace-otlp-proto is not a production dependency (precondition for via_one_agent);
  • standalone + OneAgent — the factory returns a real NodeTracerProvider whose spans are recording (not NonRecordingSpan) and whose creation/end never crashes;
  • CALM + OneAgent — rejected as an unsupported combination (throws).

…n-process capture

On the via_one_agent path (DT_NODE_PRELOAD_OPTIONS set, kind *-to-dynatrace,
no OTLP trace exporter installed), register a recording NodeTracerProvider with
an empty span-processor list instead of warning and bailing out. The provider
records but exports nothing itself, so the CDS spans exist for Dynatrace
OneAgent's in-process OpenTelemetry capture to pick up without duplicating them.
The CALM path (falsy resource) still defers to @sap/xotel-agent-ext-js.

README/CHANGELOG document that current OneAgent versions do not yet capture the
OpenTelemetry JS SDK version this package depends on, so the OTLP exporter
remains required until OneAgent adds support.
@sjvans
sjvans requested a review from a team as a code owner September 18, 2026 06:59
@hyperspace-pr-bot

Copy link
Copy Markdown
Contributor

Summary

The following content is AI-generated and provides a summary of the pull request:


Enable OneAgent In-Process Trace Recording for Dynatrace

New Features

✨ Registers a recording NodeTracerProvider on the Dynatrace OneAgent path when no OTLP trace exporter is installed. This allows CDS spans to be created for OneAgent’s in-process OpenTelemetry capture without exporting them from @cap-js/telemetry, avoiding duplicate spans.

Changes

  • lib/tracing/index.js: Updates the OneAgent path to register a real tracer provider with an empty span processor list, keeping spans recording while avoiding any export path. The CALM path remains unchanged and does not register a competing provider.
  • test/tracing-one-agent.test.js: Updates regression coverage to verify:
    • an undefined span processor still reproduces the original crash mode;
    • the standalone OneAgent path returns a recording NodeTracerProvider;
    • spans can be created and ended without crashing;
    • the CALM path returns without registering a provider.
  • README.md: Documents the new OneAgent in-process behavior and clarifies the current limitation: existing OneAgent versions do not yet capture spans from the OpenTelemetry JS SDK layout used by this package, so OTLP export remains required for CDS spans to reach Dynatrace for now.
  • CHANGELOG.md: Updates the unreleased 2.1.0 notes with the new recording-provider behavior and the OneAgent compatibility caveat.

GitHub Issues

  • #509: Re-enable Dynatrace OneAgent in-process trace capture
  • #502: Cannot read properties of undefined (reading 'onStart')
  • #504: Prior crash-safe OneAgent path fix

  • 🔄 Regenerate and Update Summary
  • ✏️ Insert as PR Description (deletes this comment)
  • 🗑️ Delete comment
PR Bot Information

Version: 1.31.43

@hyperspace-pr-bot hyperspace-pr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a likely user-facing mismatch: the startup log now says it is “relying on in-process trace capture,” while the README/CHANGELOG explicitly state current OneAgent versions do not capture these spans and OTLP remains required. Since I can’t make more tool calls, I’m not posting an inline comment, but I recommend changing that log to a warning that clearly tells users CDS spans may not reach Dynatrace without the OTLP exporter.

PR Bot Information

Version: 1.31.43

  • Event Trigger: pull_request.opened
  • LLM: gpt-5.5
  • Correlation ID: 8c6a24b0-b32e-11f1-8cfc-611f4849a691
  • File Content Strategy: Full file content

Fold the OneAgent branch into the two-axis flow (via_one_agent × resource)
instead of an early self-contained block:
- reject the unverified CALM + OneAgent combination fail-fast
- OneAgent standalone falls through to the provider path with no processor
  (spanProcessors: []), so recording works without an export path of our own
Drop the temporary caveat that the OTLP exporter is still required under
OneAgent. The in-process path is the intended behavior; the current
OneAgent-side SDK-layout gap is transient and tracked separately.
@sjvans sjvans changed the title feat(tracing): register recording provider on the OneAgent path for in-process capture feat(tracing): support Dynatrace OneAgent in-process trace capture Sep 18, 2026
@sjvans
sjvans merged commit 3ac0409 into develop Sep 18, 2026
8 checks passed
@sjvans
sjvans deleted the feat/oneagent-inprocess-trace-capture branch September 18, 2026 20:00
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