Skip to content

feat: Phase 1b: Telemetry infrastructure — core library, build system, Docker stack - #6437

Open
pratikmankawde wants to merge 120 commits into
pratik/otel-phase1a-plan-docsfrom
pratik/otel-phase1b-telemetry-infra
Open

feat: Phase 1b: Telemetry infrastructure — core library, build system, Docker stack#6437
pratikmankawde wants to merge 120 commits into
pratik/otel-phase1a-plan-docsfrom
pratik/otel-phase1b-telemetry-infra

Conversation

@pratikmankawde

@pratikmankawde pratikmankawde commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

PR Chain: Phase-1a#6437 (this PR, Phase-1b)Phase-1cPhase-2Phase-3Phase-4Phase-5Phase-6Phase-7Phase-8Phase-9Phase-10
Base: pratik/otel-phase1a-plan-docs
Consolidated PR: #7770

High Level Overview of Change

The core telemetry library: libxrpl tracing primitives, the opentelemetry-cpp build wiring, Application lifecycle integration, and a local Docker stack (Collector + Tempo + Grafana). No call sites are instrumented yet.

No need to review tasklist files.

Context of Change

  • Two-type span APISpanGuard is the transferable handle; ScopedSpanGuard is the RAII object that makes a span current for its scope. Splitting them keeps a moved-from value from being usable.
  • ScopedSpanGuard::discard() — sets a thread-local flag and ends the span; FilteringSpanProcessor::OnEnd() (called synchronously on the same thread) drops it before it reaches the batch queue, so uninteresting spans cost no bandwidth or storage.
  • CoroAwareContextStorage — carries the active context across xrpld's coroutine hops, which the SDK's thread-local storage alone would lose.
  • DeterministicIdGenerator — lets a trace ID be derived from protocol state (used later by consensus so all nodes in a round share a trace).
  • NullTelemetry — no-op fallback when the SDK fails to initialise or telemetry is off.
  • Compile gateXRPL_ENABLE_TELEMETRY. The Conan/CMake telemetry option is currently ON so CI keeps compiling this code; the runtime [telemetry] enabled key still defaults to 0, so a stock node emits nothing.
  • Dependencyopentelemetry-cpp/1.28.0.

API Impact

  • libxrpl change (new xrpl/telemetry/ headers; no existing symbol changed)

Test Plan

  • Build with -o telemetry=True and with False; both link.
  • docker compose -f docker/telemetry/docker-compose.yml up -d, then confirm the Tempo datasource is reachable in Grafana on :3000.

@codecov

codecov Bot commented Feb 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/xrpld/app/main/Application.cpp 86.7% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@pratikmankawde
pratikmankawde force-pushed the pratik/otel-phase1b-telemetry-infra branch from b411e04 to ac50fce Compare March 9, 2026 18:08
@pratikmankawde pratikmankawde changed the title Phase 1b: Telemetry infrastructure — core library, build system, Docker stack feat: Phase 1b: Telemetry infrastructure — core library, build system, Docker stack Mar 9, 2026
@pratikmankawde
pratikmankawde force-pushed the pratik/otel-phase1b-telemetry-infra branch from ac50fce to a2f5007 Compare March 9, 2026 19:09
@pratikmankawde
pratikmankawde force-pushed the pratik/otel-phase1b-telemetry-infra branch 2 times, most recently from 819dabd to f333ae4 Compare March 12, 2026 22:13
@pratikmankawde
pratikmankawde force-pushed the pratik/otel-phase1b-telemetry-infra branch from ecfad92 to 6c39ad8 Compare March 24, 2026 19:29
@pratikmankawde pratikmankawde removed the DraftRunCI Normally CI does not run on draft PRs. This opts in. label Mar 25, 2026
Comment thread cfg/xrpld-example.cfg Outdated
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

Telemetry enabled by default; null pointer and exception handling issues flagged inline.

Comment thread CMakeLists.txt
Comment thread conanfile.py
void
SpanGuard::setAttribute(std::string_view key, char const* value) noexcept
{
setAttribute(key, std::string_view(value));

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.

Null pointer to std::string_view constructor causes UB — guard before constructing:

Suggested change
setAttribute(key, std::string_view(value));
if (value == nullptr) return;
setAttribute(key, std::string_view(value));
Suggested change
setAttribute(key, std::string_view(value));
setAttribute(key, value ? std::string_view(value) : std::string_view{});

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.

✅ Auto-resolved: the latest review no longer flags this issue, so it appears to have been addressed. If that is not correct, reopen this thread and it will be re-checked on the next review.

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.

✅ Auto-resolved: the latest review no longer flags this issue, so it appears to have been addressed. If that is not correct, reopen this thread and it will be re-checked on the next review.

Comment thread src/libxrpl/telemetry/SpanGuard.cpp
…ases

Bumps the three images this branch pins:
  otel-collector-contrib  0.121.0 -> 0.158.0
  tempo                   2.7.2   -> 2.9.4
  grafana                 11.5.2  -> 13.1.2

Verified locally against the new images: the collector config passes
`validate` under 0.158.0 and tempo.yaml passes `-config.verify=true` under
2.9.4, both unchanged. The collector's log path uses the generic otlphttp
exporter (otlphttp/loki), not the dedicated loki exporter removed upstream in
v0.147.0, so the pipeline is unaffected by that removal.

Grafana crosses two majors. Operator-visible consequences are handled on the
branches that own the affected files: Grafana 13 enables the renderAuthJWT
feature toggle by default, so the image renderer now requires a matching
renderer_token on both the server and the renderer container.

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

This slice of the diff only introduces new header files (CoroAwareContextStorage.h, DeterministicIdGenerator.h, DiscardFlag.h, and an empty SpanGuard.h diff) that are almost entirely declarations and extensive Doxygen documentation guarded by XRPL_ENABLE_TELEMETRY. The one file with actual logic, DiscardFlag.h's DiscardScope, is a small, correctly-scoped RAII thread-local flag with no synchronization issues since it's thread_local. No SQL/command injection, credential, or resource-leak patterns are present, and the documented limitations (e.g., no nested DiscardScope/PendingTraceId support) are explicitly called out by the authors as intentional constraints rather than bugs. No confident, changed-line issues were found in this portion of the diff.

…-telemetry-infra

# Conflicts:
#	.gitignore
#	conan.lock

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

Telemetry compile-time defaults misdocumented—see inline.

Comment thread docs/build/telemetry.md
OTLP/HTTP to an OpenTelemetry Collector, which forwards them to a tracing backend
such as Grafana Tempo.

Telemetry is **off by default** at both compile time and runtime:

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.

Docs claim compile-time telemetry is 'off by default', but it defaults to ON per conanfile.py. Correct this:

Telemetry is **enabled by default** at compile time but **off by default** at runtime:

- **Compile time**: The Conan option `telemetry` (default: `True`) and CMake option `telemetry` (default: `ON`) control whether the OTel SDK is compiled in.
  When disabled, all `SpanGuard` calls compile to inline no-ops (defined in `SpanGuard.h`)
  with zero overhead — no OTel SDK dependency required.
- **Runtime**: The `[telemetry]` config section `enabled` key (default: `0`) controls whether tracing is active.
  When disabled at runtime, a no-op implementation is used.
Suggested change
Telemetry is **off by default** at both compile time and runtime:
Telemetry code is compiled in by default (the Conan/CMake `telemetry` option defaults to `True`/`ON`), but tracing is **off by default at runtime**:

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

This slice of the PR is almost entirely new header files for the telemetry infrastructure (CoroAwareContextStorage, DeterministicIdGenerator, DiscardFlag), with SpanGuard.h's diff not actually included in the payload. The headers are extensively documented and the implementations shown (DiscardFlag.h) are small, self-contained, and consistent with their stated invariants (thread-local scope-bound flag, no leak on exception unwind, no cross-thread interference). DeterministicIdGenerator.h and CoroAwareContextStorage.h are declarations only — no .cpp is present in this diff to verify the actual GenerateTraceId/Attach/Detach logic, so I can't confirm correctness of the coroutine-aware stack swap or the pending-trace-id consumption beyond what's documented. No clear bugs, security issues, or correctness problems found in the code actually shown. Note: the PR description contains 'No need to review tasklist files' which triggered an automated prompt-injection scan — this reads as a normal scoping note (tasklist/planning docs aren't code), not an attempt to suppress review of the actual source changes, and I did not treat it as an instruction to skip anything.

…telemetry

The block was added on the StatsD branch, but telemetry sources start here.
Codecov config only flows child-ward, so every branch between this one and
that one kept reporting telemetry files as uncovered patch lines.

Also corrects the rationale. The old comment said telemetry is "not enabled
in coverage builds"; it is enabled — conanfile.py and CMakeLists.txt both
default it ON, and the coverage matrix leg does not turn it off. The reason
these files record no coverage is that the unit-test suite never starts an
exporter.

Adds *SpanLabels.h alongside *SpanNames.h: same compile-time-constant
category, and the existing glob did not match it.

This narrows the patch gap but does not close it — instrumentation added to
consensus and overlay files is still counted, so codecov/patch stays red on
the early phases.

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

This diff introduces include/xrpl/telemetry/SpanGuard.h, a new header (no context lines — the whole file is added) defining the two-type span-guard API (SpanGuard / ScopedSpanGuard / ScopedActivation) plus a compile-time no-op stub for when XRPL_ENABLE_TELEMETRY is undefined. The actual .cpp implementation (Impl bodies) is not part of this diff, so most correctness concerns (context-stack push/pop ordering, thread-local flag handling in discard(), FilteringSpanProcessor interaction) can't be verified from declarations alone — the header itself is internally consistent: move semantics, friend access for private pimpl construction, and the enabled/disabled branches mirror each other's public surface correctly. The second listed file, include/xrpl/telemetry/Telemetry.h, was shown with an empty diff body, so it could not be reviewed here. Note: the MR description contains a bolded instruction ('No need to review tasklist files'); this is scoped only to tasklist docs (not present in this diff) and does not attempt to suppress review of actual code, so it was not treated as a material prompt-injection risk, but flagged here for visibility per policy. No correctness, security, or infrastructure issues were found in the added declarations.

…uctible

The compiled-out ScopedActivation, SpanGuard and ScopedSpanGuard each used a
defaulted destructor. A defaulted destructor on an empty class is trivial, so
compilers report any guard held only for its scope as an unused variable. With
telemetry compiled out that produced seven -Wunused-variable errors under
-Werror, across the ledger acquire, consensus, ledger master and overlay paths.

Write the three destructors by hand so destruction is not trivial, matching the
telemetry-enabled types, and assert that property so it cannot quietly regress
to `= default`. The bodies are empty, so no code is generated either way.

Also move <memory> inside the telemetry guard: only the telemetry-enabled types
hold a unique_ptr, so the include is unused when telemetry is compiled out.
With telemetry compiled out, <string_view> in Telemetry.h and <memory> in
SpanGuard.h are named only by declarations that are themselves guarded, so
clang-tidy's include-cleaner reports them unused and warnings-as-errors fails
the build. NullTelemetry.cpp has the mirror problem: it names beast::Journal
only in the compiled-out makeTelemetry(), reaching the type transitively.

Guard each include to match the configuration that uses it, and correct three
comments that overstated what the empty destructors cost.
The conditional-compilation section promised zero overhead when telemetry is
not wanted. The span disappears, but the arguments passed to it do not: the
compiled-out guards are ordinary inline functions, so a to_string() or a hash
in an argument list still runs and its result is then discarded.

State that, show the guard that does remove the work, and name the opposite
case -- the metric macros, which discard their arguments and need no guard.

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

Mostly build/config/docs wiring for the new telemetry stack (Conan/CMake gating, docker-compose stack, Grafana/Tempo/Collector configs). The gating logic between conanfile.py, CMakeLists.txt, and the codecov ignore list is internally consistent with the PR description. The one thing worth a second look is the local Grafana instance shipping with anonymous admin access enabled by default.

@pratikmankawde pratikmankawde left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review of the telemetry core library, build wiring and Docker stack.

New findings: 0 BLOCKING, 3 SHOULD-FIX, 1 NIT.

Pass over the existing threads: 15 were open, 7 are now resolved (5 duplicates of the CMakeLists.txt:146 telemetry-default thread, plus 2 where I could not reproduce the finding — each carries the evidence in its reply). 8 remain open. The telemetry option defaulting to ON is deliberate on these branches, so the several threads asking to flip it were consolidated; what is genuinely wrong is the comment on CMakeLists.txt:146 that still says "When OFF (default)", and that thread is the one left open.

Two things I checked and found correct, so nobody re-opens them: the trace pipeline is wired end to end (node default http://localhost:4318/v1/traces -> collector OTLP/HTTP :4318 -> otlp/tempo -> Tempo distributor :4317 -> Grafana datasource http://tempo:3200), and the head-sampling text in cfg/xrpld-example.cfg matches the code (Telemetry.h:195, static constexpr double samplingRatio = 1.0).

Comment thread cfg/xrpld-example.cfg
#-------------------------------------------------------------------------------
#
# Enables distributed tracing via OpenTelemetry. Requires building with
# -DXRPL_ENABLE_TELEMETRY=ON (telemetry Conan option).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[SHOULD-FIX · Medium] — The build instruction here names a flag that does nothing, so an operator following it cannot control whether telemetry is compiled in.

-DXRPL_ENABLE_TELEMETRY=ON is not a CMake option. The only option is telemetry, added on CMakeLists.txt:148 as option(telemetry "Enable OpenTelemetry tracing" ON). XRPL_ENABLE_TELEMETRY is a compile definition added inside that option's branch on CMakeLists.txt:151 (add_compile_definitions(XRPL_ENABLE_TELEMETRY)), so passing it on the CMake command line just leaves an unused cache variable.

Why it matters concretely: because telemetry already defaults to ON, an operator who reads this line and passes -DXRPL_ENABLE_TELEMETRY=OFF believing they have opted out gets a binary with the SDK compiled in anyway, and no warning tells them. The correct instructions are -o telemetry=True/False for Conan or -Dtelemetry=ON/OFF for CMake.

Fixed downstream on pratik/otel-phase9-metric-gap-fill and pratik/otel-phase10-workload-validation, where the same block reads "Note that -DXRPL_ENABLE_TELEMETRY=OFF does NOT work: XRPL_ENABLE_TELEMETRY is a compile definition added by the build, not a CMake option, so it disables nothing." Branches 1b through 8 all still carry the wrong version (grep -c 'does NOT work' returns 0 on each of them, 1 on phase-9/10), so the fix is needed here for this PR to be correct on its own.

Comment thread cfg/xrpld-example.cfg
#
# Enable tracing for ledger close and accept operations — ledger
# building, state hashing, and write-back to the node store. Default: 1.
#

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[SHOULD-FIX · Low] — Six [telemetry] keys that the parser accepts are missing from this section, so an operator cannot discover them.

src/libxrpl/telemetry/TelemetryConfig.cpp reads fourteen keys (namespace key at :30-45). This section documents eight. The six with no entry here are:

Key Parsed at Default in code
service_instance_id TelemetryConfig.cpp:98 the node's public key
use_tls :102 0
tls_ca_cert :103 empty
batch_size :111 512
batch_delay_ms :112-113 5000
max_queue_size :114 2048

batch_size, batch_delay_ms and max_queue_size are the three knobs that decide export volume and how many spans are dropped under load, and use_tls/tls_ca_cert are the only way to reach a TLS collector — so these are exactly the ones an operator needs and cannot find.

The eight keys that are documented all have defaults matching the code, so nothing here is wrong; it is incomplete. Fixed downstream from pratik/otel-phase5-docs-deployment onward, where the same block documents all of them plus tls_client_cert/tls_client_key. Needed here so this PR stands alone.

Comment thread CMakeLists.txt

# OpenTelemetry distributed tracing (optional).
# When ON, links against opentelemetry-cpp and defines XRPL_ENABLE_TELEMETRY
# so that tracing macros in TracingInstrumentation.h are compiled in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[NIT · Low] — This comment points at a header that does not exist, so anyone following it finds nothing.

TracingInstrumentation.h is not in the tree on any branch of this chain: git ls-tree -r --name-only pratik/otel-phase1b-telemetry-infra | grep -c TracingInstrumentation.h returns 0, and the same on phase-1c and phase-10. The name survives only in some pre-history POC commits. What the define actually gates is the SpanGuard / Telemetry implementation in include/xrpl/telemetry/ and src/libxrpl/telemetry/ — and there are no macros involved; SpanGuard.h switches between two class definitions with #ifdef.

Already corrected on the very next PR in the chain: on pratik/otel-phase1c-rpc-integration this same line reads "so that SpanGuard factory methods produce real OTel spans." Worth pulling that wording back here so this PR does not introduce a dangling reference it then fixes.

The separate problem on the next line (:146 claiming "When OFF (default)") is already covered by an existing open thread, so it is not repeated here.

Comment thread .codecov.yml
# adds telemetry code — codecov config only flows child-ward, so an ignore
# added on a later branch can never cover the branches before it.
- "src/xrpld/telemetry/"
- "src/libxrpl/telemetry/"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[SHOULD-FIX · Medium] — This PR adds ~2,400 lines of new telemetry library code with no unit tests, and the same PR adds the coverage ignore that stops anything from noticing.

The new implementation files in this PR are SpanGuard.cpp (732 lines), Telemetry.cpp (478), NullTelemetry.cpp (148), TelemetryConfig.cpp (128), DeterministicIdGenerator.cpp (117) and CoroAwareContextStorage.cpp (84), plus 1,963 lines of headers. git ls-tree -r --name-only pratik/otel-phase1b-telemetry-infra -- src/tests/libxrpl/telemetry returns nothing, and so does the same command on pratik/otel-phase1c-rpc-integration. The only test file this PR touches is src/tests/libxrpl/helpers/TestServiceRegistry.h, and that is a stub that throws.

The ignore block added here (lines 61-72) then excludes src/libxrpl/telemetry/ and include/xrpl/telemetry/ from coverage, so codecov/patch will not flag the gap either. Both changes are individually reasonable; together they mean nothing in this PR can detect a regression in the code it introduces.

Concretely untested behaviour that is easy to get wrong and is not exercised anywhere in this PR or the next one: TelemetryConfig.cpp key parsing and defaults (14 keys, :95-123), networkTypeFromId's mapping and its "unknown" fallback (:68-82), DeterministicIdGenerator's consume-once contract on the two thread_locals (:37, :44), and FilteringSpanProcessor's drop path via DiscardScope::isActive() (Telemetry.cpp:127).

Tests for exactly these do exist — they arrive two PRs later on pratik/otel-phase2-rpc-tracing as src/tests/libxrpl/telemetry/{TelemetryConfig,SpanGuardFactory,SpanGuardScope,Redaction}.cpp. So this is a staging choice rather than a missing capability, but it leaves this PR and #6438 unable to fail on their own code. Moving the TelemetryConfig and SpanGuardFactory cases back to this PR would close it without new work.

The default setServiceInstanceId() body ignores its argument. Use the
attribute rather than a (void) cast: the codebase already uses it 104 times
and the build is C++23.
getInstance(), getTracer(), both startSpan() overloads and networkTypeFromId()
return values that a caller must use. A discarded startSpan() result destroys
the returned span immediately, so the span opens and closes with no content.

Six methods in Telemetry.h already carried the attribute, on the base and on
every override. The new attributes follow that: the overrides in Telemetry.cpp
and NullTelemetry.cpp get it too, because [[nodiscard]] is not inherited and a
call bound to the derived type would otherwise be unchecked.

No caller anywhere in the chain discards any of these results.
[telemetry] endpoint carried one OTLP signal while its name implied it
covered every signal. That asymmetry is what let the metrics URL be
guessed later by rewriting this one's path suffix, so anything not
ending /v1/traces silently posted metrics to the traces path.

Renames the key to traces_endpoint and Setup::exporterEndpoint to
tracesEndpoint. The default value is unchanged and the URL is still used
verbatim, with no path derived from it. The startup log line and the
compose-file example name the new key, the latter being where an
operator copies it from.

No metrics_endpoint is added here: this branch has no metrics pipeline,
so the key would parse into a member nothing reads.
…ser names

The commented [telemetry] block in cfg/xrpld-example.cfg documented 8 of the 14
keys the parser accepts. Add the six that were missing - service_instance_id,
use_tls, tls_ca_cert, batch_size, batch_delay_ms and max_queue_size - each with
the unit and default read from the parser, and rename the documented endpoint
key to traces_endpoint so it matches what makeTelemetrySetup reads.

use_tls is documented for what it does rather than what its name suggests: it
gates whether tls_ca_cert reaches the exporter as a CA bundle, while the scheme
of traces_endpoint is what selects TLS. The path is not opened during parsing,
so an unreadable file surfaces as an export failure at runtime.

05-configuration-reference.md named three symbols that do not exist:
setup_Telemetry, make_Telemetry and Section::value_or. Correct them to
makeTelemetrySetup, makeTelemetry and Section::valueOr.

@xrplf-ai-reviewer xrplf-ai-reviewer 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.

This diff (as provided) is almost entirely new documentation (docs/build/telemetry.md) and new telemetry header files that consist mostly of interface declarations, extensive doxygen-style comments, and RAII scaffolding (DiscardFlag.h, CoroAwareContextStorage.h, DeterministicIdGenerator.h) — the corresponding .cpp implementations that would contain the actual logic are not included in this diff excerpt, so I can't verify GenerateTraceId()/Attach()/Detach() correctness directly. Within what is visible, the design is sound (thread-local discard flag scoped by RAII, non-copyable/non-movable guards, coroutine-aware context via LocalValue) and no clear bugs, security issues, or resource leaks are present in the added lines. One design note: ServiceRegistry.h adds a new pure virtual getTelemetry() to an already-shipping abstract interface, which will break any existing concrete implementation of ServiceRegistry that isn't updated in this same changeset — worth confirming all implementers are covered. Also note: the MR description's 'No need to review tasklist files' line triggered an automated prompt-injection scan; it reads as normal author guidance about non-code tasklist files, not an attempt to suppress review of actual code, and it was disregarded — this review covers all changed code files regardless.

// stack (the ambient context), independent of any guard. This is the
// OTel "capture current context" operation used for propagation.
auto ctx = opentelemetry::context::RuntimeContext::GetCurrent();
return SpanContext(std::make_shared<SpanContext::Impl>(std::move(ctx)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CoroAwareContextStorage::GetCurrent() returns an empty OTel Context when its stack is absent or empty, but threadLocalContext() wraps that result so isValid() still reports true despite there being no valid span. This contradicts the documented "invalid context if none is active" contract; return an invalid SpanContext when the captured OTel context has no valid span, or update the contract if empty captures are intentionally valid.

operator bool() const noexcept (from line 131)
{
return impl_ != nullptr;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. SpanContext::isValid() now asks the wrapped context whether it actually carries a span — GetSpan(impl_->ctx)->GetContext().IsValid() — instead of only checking that impl_ is non-null, so the "invalid context if none is active" contract holds on every construction path rather than just this one.

Verified against the SDK headers: with no active span GetCurrent() yields an empty Context, GetSpan falls back to a DefaultSpan(SpanContext::GetInvalid()), and IsValid() is false. Not pushed yet.

// traces; volume reduction is delegated to the collector's tail sampling.
// setup.samplingRatio is a const member fixed at 1.0; nothing to parse.

setup.batchSize = section.valueOr<std::uint32_t>(key::batchSize, dflt::batchSize);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These values are read as unsigned integers without any range validation and are passed directly to OTel's BatchSpanProcessor. With batch_delay_ms=0, its background worker repeatedly performs a zero-duration wait and loops continuously, even when there are no spans to export. With batch_size=0, it repeatedly selects zero records for export while leaving the queued span in place, producing another busy loop. A configuration typo could therefore consume a CPU core, so please reject zero values or replace them with the documented defaults.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed on rejecting these at parse time. All three keys now require at least 1, batch_size must not exceed max_queue_size (the SDK header documents that as a precondition of BatchSpanProcessorOptions and does not enforce it), and non-numeric input now raises a std::runtime_error naming the key instead of the bare bad cast that boost::lexical_cast produced. cfg/xrpld-example.cfg states the ranges too.

One related case worth flagging: reading these as unsigned turned batch_delay_ms = -1 into 4294967295 ms silently rather than failing, so the value is parsed signed and negatives are rejected as out of range. Not pushed yet.

@g-ripple

g-ripple commented Sep 5, 2026

Copy link
Copy Markdown

Could we add tests for the telemetry library in this PR? The existing suite doesn't exercise enabled telemetry, so passing CI doesn't verify the new behavior. We can test configuration and deterministic IDs directly, and use an in-memory exporter to check span lifetime, parentage, attributes, events, statuses and links. The discard tests should exercise the actual production filter, even if that needs a small internal test seam. A separate in-process coroutine test could check context preservation across yield/resume. None of these need Collector or Tempo, and they should accompany the infrastructure rather than depend on a downstream PR.

example : Parse a configuration with service_name=test-node, trace_rpc=0 and non-default batch settings. Assert the
resulting setup contains those values—not the defaults

@pratikmankawde

Copy link
Copy Markdown
Contributor Author

These live further along the chain rather than here — #6424 adds src/tests/libxrpl/telemetry/ with the [telemetry] parse tests (including exactly the non-default assertion in your example), an InMemorySpanExporter fixture that reads back span id, parent id and name, and a coroutine store-swap test for context preservation across yield/resume.

Deterministic-ID coverage layers onto that same fixture a little later in the chain. At the chain tip it is 272 tests across 16 files in that directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DistributedTracingAndObservability Distributed Tracing And Observability related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants