Merge branch 'pratik/otel-phase1b-telemetry-infra' into pratik/otel-phase1c-rpc-integration

# Conflicts:
#	include/xrpl/telemetry/Telemetry.h
This commit is contained in:
Pratik Mankawde
2026-07-20 17:17:20 +01:00
12 changed files with 527 additions and 404 deletions

View File

@@ -1,6 +1,3 @@
Loop: xrpl.telemetry xrpld.rpc
xrpld.rpc == xrpl.telemetry
Loop: xrpld.app xrpld.overlay
xrpld.app > xrpld.overlay

View File

@@ -314,6 +314,7 @@ xrpld.rpc > xrpl.rdb
xrpld.rpc > xrpl.resource
xrpld.rpc > xrpl.server
xrpld.rpc > xrpl.shamap
xrpld.rpc > xrpl.telemetry
xrpld.rpc > xrpl.tx
xrpld.shamap > xrpl.basics
xrpld.shamap > xrpld.core

View File

@@ -1,56 +1,58 @@
#pragma once
/** Thread-local discard signaling between SpanGuard and the span processor.
SpanGuard::discard() wants to drop a span without sending it to the
exporter. The OTel SDK calls SpanProcessor::OnEnd() synchronously on the
same thread that calls Span::End(), so a thread-local flag set just before
End() and read inside OnEnd() lets FilteringSpanProcessor drop the span
before it enters the batch export queue.
This side-channel avoids inspecting the Recordable's internals (which vary
by exporter type — SpanData vs OtlpRecordable).
The flag is a *private* thread-local member of DiscardScope, mutated only by
its constructor and destructor. This gives real access control rather than a
naming convention: no code that includes this header can flip the flag
directly — it can only enter a DiscardScope (which sets and clears the flag
over its own lifetime) and observe the state via DiscardScope::isActive().
Binding set/clear to a scope also means the flag cannot leak onto the next
span even if End() were to throw.
Kept in a separate header to avoid transitive include bloat: SpanGuard.h
only needs this signaling, not the full Telemetry.h with BasicConfig/Journal.
Usage:
@code
// In SpanGuard::discard():
{
DiscardScope discardScope; // flag set for this scope only
span->End(); // OnEnd() runs synchronously, sees flag
} // flag cleared here, unconditionally
// In FilteringSpanProcessor::OnEnd():
if (DiscardScope::isActive())
return; // drop the span
@endcode
@note Thread safety: the flag is thread-local, so each thread observes only
its own discard signal — no synchronization is required.
@see SpanGuard::discard(), FilteringSpanProcessor (Telemetry.cpp)
*/
/**
* Thread-local discard signaling between SpanGuard and the span processor.
*
* SpanGuard::discard() wants to drop a span without sending it to the
* exporter. The OTel SDK calls SpanProcessor::OnEnd() synchronously on the
* same thread that calls Span::End(), so a thread-local flag set just before
* End() and read inside OnEnd() lets FilteringSpanProcessor drop the span
* before it enters the batch export queue.
*
* This side-channel avoids inspecting the Recordable's internals (which vary
* by exporter type — SpanData vs OtlpRecordable).
*
* The flag is a *private* thread-local member of DiscardScope, mutated only by
* its constructor and destructor. This gives real access control rather than a
* naming convention: no code that includes this header can flip the flag
* directly — it can only enter a DiscardScope (which sets and clears the flag
* over its own lifetime) and observe the state via DiscardScope::isActive().
* Binding set/clear to a scope also means the flag cannot leak onto the next
* span even if End() were to throw.
*
* Kept in a separate header to avoid transitive include bloat: SpanGuard.h
* only needs this signaling, not the full Telemetry.h with BasicConfig/Journal.
*
* Usage:
* @code
* // In SpanGuard::discard():
* {
* DiscardScope discardScope; // flag set for this scope only
* span->End(); // OnEnd() runs synchronously, sees flag
* } // flag cleared here, unconditionally
*
* // In FilteringSpanProcessor::OnEnd():
* if (DiscardScope::isActive())
* return; // drop the span
* @endcode
*
* @note Thread safety: the flag is thread-local, so each thread observes only
* its own discard signal — no synchronization is required.
*
* @see SpanGuard::discard(), FilteringSpanProcessor (Telemetry.cpp)
*/
namespace xrpl::telemetry {
/** RAII guard that marks the current thread's span for discard.
Sets the thread-local discard flag on construction and clears it on
destruction, so a span ended within the guard's scope is dropped by
FilteringSpanProcessor::OnEnd() while the flag stays confined to that scope.
The flag is private and mutated only here, so no other code can set it.
Non-copyable and non-movable — its sole purpose is the scoped flag lifetime.
*/
/**
* RAII guard that marks the current thread's span for discard.
*
* Sets the thread-local discard flag on construction and clears it on
* destruction, so a span ended within the guard's scope is dropped by
* FilteringSpanProcessor::OnEnd() while the flag stays confined to that scope.
* The flag is private and mutated only here, so no other code can set it.
* Non-copyable and non-movable — its sole purpose is the scoped flag lifetime.
*/
class DiscardScope
{
public:
@@ -71,9 +73,11 @@ public:
DiscardScope&
operator=(DiscardScope&&) = delete;
/** @return true if the current thread is inside a DiscardScope, i.e. the
span ending now should be dropped rather than exported. Read by
FilteringSpanProcessor::OnEnd(). */
/**
* @return true if the current thread is inside a DiscardScope, i.e. the
* span ending now should be dropped rather than exported. Read by
* FilteringSpanProcessor::OnEnd().
*/
[[nodiscard]] static bool
isActive() noexcept
{
@@ -81,8 +85,10 @@ public:
}
private:
/** Thread-local discard flag. Private, so only this class's ctor/dtor can
mutate it; observers use isActive(). One instance per thread. */
/**
* Thread-local discard flag. Private, so only this class's ctor/dtor can
* mutate it; observers use isActive(). One instance per thread.
*/
inline static thread_local bool discarding = false;
};

View File

@@ -1,18 +1,19 @@
#pragma once
/** Compile-time string concatenation utility and shared telemetry constants.
/**
* Compile-time string concatenation utility and shared telemetry constants.
*
* Provides StaticStr<N> — a compile-time string buffer that implicitly
* converts to std::string_view — and join() for dot-separated concatenation.
* Module-specific span names (e.g. RPC, consensus) live in their respective
* modules and build upon these shared primitives.
*
* @note These constants are NOT guarded by XRPL_ENABLE_TELEMETRY because
* @note These constants are NOT guarded by XRPL_ENABLE_TELEMETRY because
* call sites reference them even when SpanGuard methods are no-ops
* (the no-op stubs still accept string_view parameters). The compiler
* elides all inline constexpr values whose only uses are in dead code.
*
* @note Json::StaticString (jss.h) is a pointer wrapper without
* @note Json::StaticString (jss.h) is a pointer wrapper without
* concatenation support. boost::static_string is not constexpr.
* StaticStr<N> exists specifically for compile-time dot-join composition.
*
@@ -37,8 +38,10 @@ namespace xrpl::telemetry {
// ===== Compile-time string utility =========================================
/// Fixed-size character buffer for compile-time string operations.
/// Implicitly converts to std::string_view at zero cost.
/**
* Fixed-size character buffer for compile-time string operations.
* Implicitly converts to std::string_view at zero cost.
*/
template <std::size_t N>
struct StaticStr
{
@@ -60,11 +63,15 @@ struct StaticStr
}
};
/// Deduction guide: StaticStr from string literal.
/**
* Deduction guide: StaticStr from string literal.
*/
template <std::size_t N>
StaticStr(char const (&)[N]) -> StaticStr<N - 1>;
/// Create a StaticStr from a string literal.
/**
* Create a StaticStr from a string literal.
*/
template <std::size_t N>
constexpr auto
makeStr(char const (&str)[N])
@@ -72,7 +79,9 @@ makeStr(char const (&str)[N])
return StaticStr<N - 1>(str);
}
/// Concatenate two StaticStr values with a dot separator.
/**
* Concatenate two StaticStr values with a dot separator.
*/
template <std::size_t A, std::size_t B>
constexpr auto
join(StaticStr<A> const& lhs, StaticStr<B> const& rhs)

View File

@@ -1,90 +1,91 @@
#pragma once
/** Abstract interface for OpenTelemetry distributed tracing.
Provides the Telemetry base class that all components use to create trace
spans. Three concrete implementations exist, selected at construction time
by makeTelemetry():
- TelemetryImpl (Telemetry.cpp): real OTel SDK integration, compiled
only when XRPL_ENABLE_TELEMETRY is defined and enabled at runtime.
- NullTelemetry (NullTelemetry.cpp): no-op stub used when telemetry is
disabled at compile time or runtime.
- NullTelemetryOtel (Telemetry.cpp): no-op stub that still depends on
the OTel API (used during transition or for testing).
Inheritance / dependency diagram:
+--------------------+
| Telemetry | (abstract, this file)
| <<interface>> |
+---------+----------+
|
+---------+-----------+-------------------+
| | |
+---+------------+ +-----+---------+ +------+----------+
| TelemetryImpl | | NullTelemetry | | NullTelemetryOtel|
| (Telemetry.cpp)| |(NullTelemetry | | (Telemetry.cpp) |
| OTel SDK | | .cpp) | | noop w/ OTel API |
+----------------+ +---------------+ +------------------+
The Setup struct holds all configuration parsed from the [telemetry]
section of xrpld.cfg. See TelemetryConfig.cpp for the parser and
cfg/xrpld-example.cfg for the available options.
OTel SDK headers are conditionally included behind XRPL_ENABLE_TELEMETRY
so that builds without telemetry have zero dependency on opentelemetry-cpp.
Usage examples:
1. Root span at a subsystem entry point (typical usage):
@code
#include <xrpld/rpc/detail/RpcSpanNames.h>
using namespace xrpl::telemetry;
// In an RPC handler dispatch:
auto guard = SpanGuard::span(
TraceCategory::Rpc, rpc_span::prefix::command, commandName);
guard.setAttribute(rpc_span::attr::command, commandName);
// ... process request
// guard destructor automatically ends the span on scope exit
@endcode
2. Child span for a sub-operation (scoped child):
@code
auto parent = SpanGuard::span(
TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::process);
{
auto child = parent.childSpan(rpc_span::op::process);
child.setAttribute(rpc_span::attr::version, apiVersion);
// child ends here
}
@endcode
3. Unrelated span (cross-scope, same thread):
@code
// gRPC and RPC handlers can be active simultaneously
auto grpcSpan = SpanGuard::span(
TraceCategory::Rpc, grpc_span::prefix::grpc, grpc_span::attr::method);
auto rpcSpan = SpanGuard::span(
TraceCategory::Rpc, rpc_span::prefix::command, commandName);
// both spans end on scope exit
@endcode
4. Cross-thread context propagation:
@code
// Thread A: capture the active context while span is in scope
auto ctx = parentGuard.captureContext();
// Thread B: create child span with explicit parent
auto child = SpanGuard::childSpan(rpc_span::op::process, ctx);
@endcode
@note Thread safety: The Telemetry interface is safe for concurrent reads
(isEnabled, shouldTrace*, getTracer, startSpan) after start() completes.
setServiceInstanceId() must be called before start() and is not thread-safe.
The OTel SDK's TracerProvider and Tracer are internally thread-safe.
*/
/**
* Abstract interface for OpenTelemetry distributed tracing.
*
* Provides the Telemetry base class that all components use to create trace
* spans. Three concrete implementations exist, selected at construction time
* by makeTelemetry():
*
* - TelemetryImpl (Telemetry.cpp): real OTel SDK integration, compiled
* only when XRPL_ENABLE_TELEMETRY is defined and enabled at runtime.
* - NullTelemetry (NullTelemetry.cpp): no-op stub used when telemetry is
* disabled at compile time or runtime.
* - NullTelemetryOtel (Telemetry.cpp): no-op stub that still depends on
* the OTel API (used during transition or for testing).
*
* Inheritance / dependency diagram:
*
* +--------------------+
* | Telemetry | (abstract, this file)
* | <<interface>> |
* +---------+----------+
* |
* +---------+-----------+-------------------+
* | | |
* +---+------------+ +-----+---------+ +------+----------+
* | TelemetryImpl | | NullTelemetry | | NullTelemetryOtel|
* | (Telemetry.cpp)| |(NullTelemetry | | (Telemetry.cpp) |
* | OTel SDK | | .cpp) | | noop w/ OTel API |
* +----------------+ +---------------+ +------------------+
*
* The Setup struct holds all configuration parsed from the [telemetry]
* section of xrpld.cfg. See TelemetryConfig.cpp for the parser and
* cfg/xrpld-example.cfg for the available options.
*
* OTel SDK headers are conditionally included behind XRPL_ENABLE_TELEMETRY
* so that builds without telemetry have zero dependency on opentelemetry-cpp.
*
* Usage examples:
*
* 1. Root span at a subsystem entry point (typical usage):
* @code
* #include <xrpld/rpc/detail/RpcSpanNames.h>
* using namespace xrpl::telemetry;
*
* // In an RPC handler dispatch:
* auto guard = SpanGuard::span(
* TraceCategory::Rpc, rpc_span::prefix::command, commandName);
* guard.setAttribute(rpc_span::attr::command, commandName);
* // ... process request
* // guard destructor automatically ends the span on scope exit
* @endcode
*
* 2. Child span for a sub-operation (scoped child):
* @code
* auto parent = SpanGuard::span(
* TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::process);
* {
* auto child = parent.childSpan(rpc_span::op::process);
* child.setAttribute(rpc_span::attr::version, apiVersion);
* // child ends here
* }
* @endcode
*
* 3. Unrelated span (cross-scope, same thread):
* @code
* // gRPC and RPC handlers can be active simultaneously
* auto grpcSpan = SpanGuard::span(
* TraceCategory::Rpc, grpc_span::prefix::grpc, grpc_span::attr::method);
* auto rpcSpan = SpanGuard::span(
* TraceCategory::Rpc, rpc_span::prefix::command, commandName);
* // both spans end on scope exit
* @endcode
*
* 4. Cross-thread context propagation:
* @code
* // Thread A: capture the active context while span is in scope
* auto ctx = parentGuard.captureContext();
*
* // Thread B: create child span with explicit parent
* auto child = SpanGuard::childSpan(rpc_span::op::process, ctx);
* @endcode
*
* @note Thread safety: The Telemetry interface is safe for concurrent reads
* (isEnabled, shouldTrace*, getTracer, startSpan) after start() completes.
* setServiceInstanceId() must be called before start() and is not thread-safe.
* The OTel SDK's TracerProvider and Tracer are internally thread-safe.
*/
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/config/BasicConfig.h>
@@ -107,129 +108,171 @@
namespace xrpl::telemetry {
#ifdef XRPL_ENABLE_TELEMETRY
/** OTel instrumentation scope (tracer) name. Identifies this library as the
source of spans; distinct from the `service.name` resource attribute
(Setup::serviceName), which is config-overridable. */
/**
* OTel instrumentation scope (tracer) name. Identifies this library as the
* source of spans; distinct from the `service.name` resource attribute
* (Setup::serviceName), which is config-overridable.
*/
inline constexpr std::string_view kTracerName{"xrpld"};
#endif
class Telemetry
{
/** Global singleton pointer, set by start()/stop() in the active
implementation. Allows SpanGuard factory methods to access the
Telemetry instance without callers passing it explicitly.
Atomic with acquire/release ordering: start()/stop() store on
the initialization thread, factory methods load on worker threads.
@see setInstance(), getInstance()
*/
/**
* Global singleton pointer, set by start()/stop() in the active
* implementation. Allows SpanGuard factory methods to access the
* Telemetry instance without callers passing it explicitly.
*
* Atomic with acquire/release ordering: start()/stop() store on
* the initialization thread, factory methods load on worker threads.
* @see setInstance(), getInstance()
*/
inline static std::atomic<Telemetry*> instance{nullptr};
public:
/** Get the global Telemetry instance.
@return Pointer to the active instance, or nullptr if not started.
*/
/**
* Get the global Telemetry instance.
* @return Pointer to the active instance, or nullptr if not started.
*/
static Telemetry*
getInstance()
{
return instance.load(std::memory_order_acquire);
}
/** Set the global Telemetry instance.
Called by start()/stop() in concrete implementations.
Tests can call this with a mock to override the global instance.
@param t Pointer to the Telemetry instance, or nullptr to clear.
*/
/**
* Set the global Telemetry instance.
* Called by start()/stop() in concrete implementations.
* Tests can call this with a mock to override the global instance.
* @param t Pointer to the Telemetry instance, or nullptr to clear.
*/
static void
setInstance(Telemetry* t)
{
instance.store(t, std::memory_order_release);
}
/** Configuration parsed from the [telemetry] section of xrpld.cfg.
All fields have sensible defaults so the section can be minimal
or omitted entirely. See TelemetryConfig.cpp for the parser.
*/
/**
* Configuration parsed from the [telemetry] section of xrpld.cfg.
*
* All fields have sensible defaults so the section can be minimal
* or omitted entirely. See TelemetryConfig.cpp for the parser.
*/
struct Setup
{
/** Master switch: true to enable tracing at runtime. */
/**
* Master switch: true to enable tracing at runtime.
*/
bool enabled = false;
/** OTel resource attribute `service.name`. */
/**
* OTel resource attribute `service.name`.
*/
std::string serviceName = "xrpld";
/** OTel resource attribute `service.version` (set from BuildInfo). */
/**
* OTel resource attribute `service.version` (set from BuildInfo).
*/
std::string serviceVersion;
/** OTel resource attribute `service.instance.id` (defaults to node
public key). */
/**
* OTel resource attribute `service.instance.id` (defaults to node
* public key).
*/
std::string serviceInstanceId;
/** OTLP/HTTP endpoint URL where spans are sent. */
/**
* OTLP/HTTP endpoint URL where spans are sent.
*/
std::string exporterEndpoint = "http://localhost:4318/v1/traces";
/** Whether to use TLS for the exporter connection. */
/**
* Whether to use TLS for the exporter connection.
*/
bool useTls = false;
/** Path to a CA certificate bundle for TLS verification. */
/**
* Path to a CA certificate bundle for TLS verification.
*/
std::string tlsCertPath;
/** Head-based sampling ratio. Intentionally fixed at 1.0 (sample
everything) and NOT read from config. A per-node ratio would let
nodes make divergent keep/drop decisions for the same distributed
trace, producing broken/partial traces. The ratio sampler is wrapped
in a ParentBasedSampler (see Telemetry.cpp) so spans inheriting a
remote parent honor the upstream sampled flag. Volume reduction is
delegated to the collector's tail sampling; for node-local post-hoc
dropping see SpanGuard::discard().
*/
/**
* Head-based sampling ratio. Intentionally fixed at 1.0 (sample
* everything) and NOT read from config. A per-node ratio would let
* nodes make divergent keep/drop decisions for the same distributed
* trace, producing broken/partial traces. The ratio sampler is wrapped
* in a ParentBasedSampler (see Telemetry.cpp) so spans inheriting a
* remote parent honor the upstream sampled flag. Volume reduction is
* delegated to the collector's tail sampling; for node-local post-hoc
* dropping see SpanGuard::discard().
*/
static constexpr double samplingRatio = 1.0;
/** Maximum number of spans per batch export. */
/**
* Maximum number of spans per batch export.
*/
std::uint32_t batchSize = 512;
/** Delay between batch exports. */
/**
* Delay between batch exports.
*/
std::chrono::milliseconds batchDelay = std::chrono::milliseconds{5000};
/** Maximum number of spans queued before dropping. */
/**
* Maximum number of spans queued before dropping.
*/
std::uint32_t maxQueueSize = 2048;
/** Network identifier, added as an OTel resource attribute. */
/**
* Network identifier, added as an OTel resource attribute.
*/
std::uint32_t networkId = 0;
/** Network type label (e.g. "mainnet", "testnet", "devnet"). */
/**
* Network type label (e.g. "mainnet", "testnet", "devnet").
*/
std::string networkType = "mainnet";
/** Enable tracing for transaction processing. */
/**
* Enable tracing for transaction processing.
*/
bool traceTransactions = true;
/** Enable tracing for consensus rounds. */
/**
* Enable tracing for consensus rounds.
*/
bool traceConsensus = true;
/** Enable tracing for RPC request handling. */
/**
* Enable tracing for RPC request handling.
*/
bool traceRpc = true;
/** Enable tracing for peer-to-peer messages (enabled by default;
high volume). */
/**
* Enable tracing for peer-to-peer messages (enabled by default;
* high volume).
*/
bool tracePeer = true;
/** Enable tracing for ledger close/accept. */
/**
* Enable tracing for ledger close/accept.
*/
bool traceLedger = true;
};
virtual ~Telemetry() = default;
/** Update the service instance ID (OTel resource attribute
`service.instance.id`).
Must be called before start(). The node public key is not available
when Telemetry is constructed (during the ApplicationImp member
initializer list), so this setter allows Application::setup() to
inject the identity once nodeIdentity_ is known.
@param id The node's base58-encoded public key or custom identifier.
*/
/**
* Update the service instance ID (OTel resource attribute
* `service.instance.id`).
*
* Must be called before start(). The node public key is not available
* when Telemetry is constructed (during the ApplicationImp member
* initializer list), so this setter allows Application::setup() to
* inject the identity once nodeIdentity_ is known.
*
* @param id The node's base58-encoded public key or custom identifier.
*/
virtual void
setServiceInstanceId(std::string const& id)
{
@@ -237,80 +280,97 @@ public:
(void)id;
}
/** Initialize the tracing pipeline (exporter, processor, provider).
Call after construction.
*/
/**
* Initialize the tracing pipeline (exporter, processor, provider).
* Call after construction.
*/
virtual void
start() = 0;
/** Flush pending spans and shut down the tracing pipeline.
Call before destruction.
*/
/**
* Flush pending spans and shut down the tracing pipeline.
* Call before destruction.
*/
virtual void
stop() = 0;
/** @return true if this instance is actively exporting spans. */
/**
* @return true if this instance is actively exporting spans.
*/
[[nodiscard]] virtual bool
isEnabled() const = 0;
/** @return true if transaction processing should be traced. */
/**
* @return true if transaction processing should be traced.
*/
[[nodiscard]] virtual bool
shouldTraceTransactions() const = 0;
/** @return true if consensus rounds should be traced. */
/**
* @return true if consensus rounds should be traced.
*/
[[nodiscard]] virtual bool
shouldTraceConsensus() const = 0;
/** @return true if RPC request handling should be traced. */
/**
* @return true if RPC request handling should be traced.
*/
[[nodiscard]] virtual bool
shouldTraceRpc() const = 0;
/** @return true if peer-to-peer messages should be traced. */
/**
* @return true if peer-to-peer messages should be traced.
*/
[[nodiscard]] virtual bool
shouldTracePeer() const = 0;
/** @return true if ledger close/accept should be traced. */
/**
* @return true if ledger close/accept should be traced.
*/
[[nodiscard]] virtual bool
shouldTraceLedger() const = 0;
#ifdef XRPL_ENABLE_TELEMETRY
/** Get or create a named tracer instance.
@param name Tracer name used to identify the instrumentation library.
@return A shared pointer to the Tracer.
*/
/**
* Get or create a named tracer instance.
*
* @param name Tracer name used to identify the instrumentation library.
* @return A shared pointer to the Tracer.
*/
virtual opentelemetry::nostd::shared_ptr<opentelemetry::trace::Tracer>
getTracer(std::string_view name = kTracerName) = 0;
/** Start a new span on the current thread's context.
The span becomes a child of the current active span (if any) via
OpenTelemetry's context propagation.
@param name Span name (typically "rpc.command.<cmd>").
@param kind The span kind (defaults to kInternal). Possible values:
- kInternal: default, in-process operation
- kServer: incoming synchronous request (e.g. RPC)
- kClient: outgoing synchronous request
- kProducer: async message send (e.g. peer broadcast)
- kConsumer: async message receive
@return A shared pointer to the new Span.
*/
/**
* Start a new span on the current thread's context.
*
* The span becomes a child of the current active span (if any) via
* OpenTelemetry's context propagation.
*
* @param name Span name (typically "rpc.command.<cmd>").
* @param kind The span kind (defaults to kInternal). Possible values:
* - kInternal: default, in-process operation
* - kServer: incoming synchronous request (e.g. RPC)
* - kClient: outgoing synchronous request
* - kProducer: async message send (e.g. peer broadcast)
* - kConsumer: async message receive
* @return A shared pointer to the new Span.
*/
virtual opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>
startSpan(
std::string_view name,
opentelemetry::trace::SpanKind kind = opentelemetry::trace::SpanKind::kInternal) = 0;
/** Start a new span with an explicit parent context.
Use this overload when the parent span is not on the current
thread's context stack (e.g. cross-thread trace propagation).
@param name Span name.
@param parentContext The parent span's context.
@param kind The span kind (defaults to kInternal).
@return A shared pointer to the new Span.
*/
/**
* Start a new span with an explicit parent context.
*
* Use this overload when the parent span is not on the current
* thread's context stack (e.g. cross-thread trace propagation).
*
* @param name Span name.
* @param parentContext The parent span's context.
* @param kind The span kind (defaults to kInternal).
* @return A shared pointer to the new Span.
*/
virtual opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>
startSpan(
std::string_view name,
@@ -319,26 +379,28 @@ public:
#endif
};
/** Create a Telemetry instance.
Returns a TelemetryImpl when setup.enabled is true, or a
NullTelemetry no-op stub otherwise.
@param setup Configuration from the [telemetry] config section.
@param journal Journal for log output during initialization.
*/
/**
* Create a Telemetry instance.
*
* Returns a TelemetryImpl when setup.enabled is true, or a
* NullTelemetry no-op stub otherwise.
*
* @param setup Configuration from the [telemetry] config section.
* @param journal Journal for log output during initialization.
*/
std::unique_ptr<Telemetry>
makeTelemetry(Telemetry::Setup const& setup, beast::Journal journal);
/** Parse the [telemetry] config section into a Setup struct.
@param section The [telemetry] config section.
@param nodePublicKey Node public key, used as default instance ID.
@param version Build version string.
@param networkId Network identifier from [network_id] config
(0 = mainnet, 1 = testnet, 2 = devnet).
@return A populated Setup struct with defaults for missing values.
*/
/**
* Parse the [telemetry] config section into a Setup struct.
*
* @param section The [telemetry] config section.
* @param nodePublicKey Node public key, used as default instance ID.
* @param version Build version string.
* @param networkId Network identifier from [network_id] config
* (0 = mainnet, 1 = testnet, 2 = devnet).
* @return A populated Setup struct with defaults for missing values.
*/
Telemetry::Setup
makeTelemetrySetup(
Section const& section,

View File

@@ -1,14 +1,15 @@
/** No-op implementation of the Telemetry interface.
Always compiled (regardless of XRPL_ENABLE_TELEMETRY). Provides the
makeTelemetry() factory when telemetry is compiled out (#ifndef), which
unconditionally returns a NullTelemetry that does nothing.
When XRPL_ENABLE_TELEMETRY IS defined, the OTel virtual methods
(getTracer, startSpan) return noop tracers/spans. The makeTelemetry()
factory in this file is not used in that case -- Telemetry.cpp provides
its own factory that can return the real TelemetryImpl.
*/
/**
* No-op implementation of the Telemetry interface.
*
* Always compiled (regardless of XRPL_ENABLE_TELEMETRY). Provides the
* makeTelemetry() factory when telemetry is compiled out (#ifndef), which
* unconditionally returns a NullTelemetry that does nothing.
*
* When XRPL_ENABLE_TELEMETRY IS defined, the OTel virtual methods
* (getTracer, startSpan) return noop tracers/spans. The makeTelemetry()
* factory in this file is not used in that case -- Telemetry.cpp provides
* its own factory that can return the real TelemetryImpl.
*/
#include <xrpl/telemetry/Telemetry.h>
@@ -30,14 +31,17 @@ namespace xrpl::telemetry {
namespace {
/** No-op Telemetry that returns immediately from every method.
Used as the sole implementation when XRPL_ENABLE_TELEMETRY is not
defined, or as a fallback when it is defined but enabled=0.
*/
/**
* No-op Telemetry that returns immediately from every method.
*
* Used as the sole implementation when XRPL_ENABLE_TELEMETRY is not
* defined, or as a fallback when it is defined but enabled=0.
*/
class NullTelemetry : public Telemetry
{
/** Retained configuration (unused, kept for diagnostic access). */
/**
* Retained configuration (unused, kept for diagnostic access).
*/
Setup const setup_;
public:
@@ -123,9 +127,10 @@ public:
} // namespace
/** Factory used when XRPL_ENABLE_TELEMETRY is not defined.
Unconditionally returns a NullTelemetry instance.
*/
/**
* Factory used when XRPL_ENABLE_TELEMETRY is not defined.
* Unconditionally returns a NullTelemetry instance.
*/
#ifndef XRPL_ENABLE_TELEMETRY
std::unique_ptr<Telemetry>
makeTelemetry(Telemetry::Setup const& setup, beast::Journal)

View File

@@ -1,17 +1,18 @@
/** OpenTelemetry SDK implementation of the Telemetry interface.
Compiled only when XRPL_ENABLE_TELEMETRY is defined (via CMake
telemetry=ON). Contains:
- FilteringSpanProcessor: decorator that drops spans marked with
kDiscardedAttr before they enter the batch export queue.
- TelemetryImpl: configures the OTel SDK with an OTLP/HTTP exporter,
FilteringSpanProcessor wrapping a batch span processor,
trace-ID-ratio sampler, and resource attributes.
- NullTelemetryOtel: no-op fallback used when telemetry is compiled in
but disabled at runtime (enabled=0 in config).
- makeTelemetry(): factory that selects the appropriate implementation.
*/
/**
* OpenTelemetry SDK implementation of the Telemetry interface.
*
* Compiled only when XRPL_ENABLE_TELEMETRY is defined (via CMake
* telemetry=ON). Contains:
*
* - FilteringSpanProcessor: decorator that drops spans marked with
* kDiscardedAttr before they enter the batch export queue.
* - TelemetryImpl: configures the OTel SDK with an OTLP/HTTP exporter,
* FilteringSpanProcessor wrapping a batch span processor,
* trace-ID-ratio sampler, and resource attributes.
* - NullTelemetryOtel: no-op fallback used when telemetry is compiled in
* but disabled at runtime (enabled=0 in config).
* - makeTelemetry(): factory that selects the appropriate implementation.
*/
#ifdef XRPL_ENABLE_TELEMETRY
@@ -60,39 +61,40 @@ namespace trace_sdk = opentelemetry::sdk::trace;
namespace otlp_http = opentelemetry::exporter::otlp;
namespace resource = opentelemetry::sdk::resource;
/** SpanProcessor decorator that drops discarded spans.
Wraps a delegate processor (typically BatchSpanProcessor). In OnEnd(),
calls DiscardScope::isActive(). If the calling thread is inside a
DiscardScope (entered by SpanGuard::discard()), the span is silently
dropped — never entering the batch queue, never sent over the network,
never stored.
Uses a thread-local flag rather than inspecting Recordable attributes
because the Recordable type varies by exporter (SpanData for simple
exporters, OtlpRecordable for OTLP) and none expose a uniform getter.
The flag is safe because Span::End() calls OnEnd() synchronously on
the same thread.
All other methods delegate directly to the wrapped processor.
Dependency diagram:
+---------------------------+
| FilteringSpanProcessor |
+---------------------------+
| - delegate_ : unique_ptr |
| <SpanProcessor> |
+---------------------------+
| wraps
+---------+-----------+
| BatchSpanProcessor |
+---------------------+
@note Thread safety: OnEnd() may be called concurrently from multiple
threads. The discard flag behind DiscardScope is thread-local, so each
thread's discard state is independent — no synchronization needed.
*/
/**
* SpanProcessor decorator that drops discarded spans.
*
* Wraps a delegate processor (typically BatchSpanProcessor). In OnEnd(),
* calls DiscardScope::isActive(). If the calling thread is inside a
* DiscardScope (entered by SpanGuard::discard()), the span is silently
* dropped — never entering the batch queue, never sent over the network,
* never stored.
*
* Uses a thread-local flag rather than inspecting Recordable attributes
* because the Recordable type varies by exporter (SpanData for simple
* exporters, OtlpRecordable for OTLP) and none expose a uniform getter.
* The flag is safe because Span::End() calls OnEnd() synchronously on
* the same thread.
*
* All other methods delegate directly to the wrapped processor.
*
* Dependency diagram:
*
* +---------------------------+
* | FilteringSpanProcessor |
* +---------------------------+
* | - delegate_ : unique_ptr |
* | <SpanProcessor> |
* +---------------------------+
* | wraps
* +---------+-----------+
* | BatchSpanProcessor |
* +---------------------+
*
* @note Thread safety: OnEnd() may be called concurrently from multiple
* threads. The discard flag behind DiscardScope is thread-local, so each
* thread's discard state is independent — no synchronization needed.
*/
class FilteringSpanProcessor : public trace_sdk::SpanProcessor
{
std::unique_ptr<trace_sdk::SpanProcessor> delegate_;
@@ -144,15 +146,18 @@ public:
}
};
/** No-op implementation used when XRPL_ENABLE_TELEMETRY is defined but
setup.enabled is false at runtime.
Lives in the anonymous namespace so there is no ODR conflict with the
NullTelemetry in NullTelemetry.cpp.
*/
/**
* No-op implementation used when XRPL_ENABLE_TELEMETRY is defined but
* setup.enabled is false at runtime.
*
* Lives in the anonymous namespace so there is no ODR conflict with the
* NullTelemetry in NullTelemetry.cpp.
*/
class NullTelemetryOtel : public Telemetry
{
/** Retained configuration (unused, kept for diagnostic access). */
/**
* Retained configuration (unused, kept for diagnostic access).
*/
Setup const setup_;
public:
@@ -230,27 +235,32 @@ public:
}
};
/** Full OTel SDK implementation that exports trace spans via OTLP/HTTP.
Configures an OTLP/HTTP exporter, batch span processor,
TraceIdRatioBasedSampler, and resource attributes on start().
*/
/**
* Full OTel SDK implementation that exports trace spans via OTLP/HTTP.
*
* Configures an OTLP/HTTP exporter, batch span processor,
* TraceIdRatioBasedSampler, and resource attributes on start().
*/
class TelemetryImpl : public Telemetry
{
/** Configuration from the [telemetry] config section.
Non-const so setServiceInstanceId() can update the instance ID
before start() creates the OTel resource.
*/
/**
* Configuration from the [telemetry] config section.
* Non-const so setServiceInstanceId() can update the instance ID
* before start() creates the OTel resource.
*/
Setup setup_;
/** Journal used for log output during start/stop. */
/**
* Journal used for log output during start/stop.
*/
beast::Journal const journal_;
/** The SDK TracerProvider that owns the export pipeline.
Held as std::shared_ptr so we can call ForceFlush() on shutdown.
Wrapped in a nostd::shared_ptr when registered as the global provider.
*/
/**
* The SDK TracerProvider that owns the export pipeline.
*
* Held as std::shared_ptr so we can call ForceFlush() on shutdown.
* Wrapped in a nostd::shared_ptr when registered as the global provider.
*/
std::shared_ptr<trace_sdk::TracerProvider> sdkProvider_;
public:

View File

@@ -1,11 +1,12 @@
/** Parser for the [telemetry] section of xrpld.cfg.
Reads configuration values from the config file and populates a
Telemetry::Setup struct. All options have sensible defaults so the
section can be minimal or omitted entirely.
See cfg/xrpld-example.cfg for the full list of available options.
*/
/**
* Parser for the [telemetry] section of xrpld.cfg.
*
* Reads configuration values from the config file and populates a
* Telemetry::Setup struct. All options have sensible defaults so the
* section can be minimal or omitted entirely.
*
* See cfg/xrpld-example.cfg for the full list of available options.
*/
#include <xrpl/config/BasicConfig.h>
#include <xrpl/telemetry/Telemetry.h>
@@ -18,13 +19,14 @@ namespace xrpl::telemetry {
namespace {
/** Config key names for the [telemetry] section.
Each must match the corresponding option documented in
cfg/xrpld-example.cfg verbatim. Defined as `char const*` so they
pass to Section::valueOr() (which takes `std::string const&`)
without an explicit conversion, exactly as a literal would.
*/
/**
* Config key names for the [telemetry] section.
*
* Each must match the corresponding option documented in
* cfg/xrpld-example.cfg verbatim. Defined as `char const*` so they
* pass to Section::valueOr() (which takes `std::string const&`)
* without an explicit conversion, exactly as a literal would.
*/
namespace key {
constexpr char const* enabled = "enabled";
constexpr char const* serviceName = "service_name";
@@ -42,13 +44,14 @@ constexpr char const* tracePeer = "trace_peer";
constexpr char const* traceLedger = "trace_ledger";
} // namespace key
/** Default values applied when a key is absent from the config.
@note serviceName mirrors SystemParameters' systemName() ("xrpld") but
is duplicated here as a literal: the telemetry module deliberately does
not link xrpl.libxrpl.protocol, so including SystemParameters.h would
introduce an undeclared cross-module dependency.
*/
/**
* Default values applied when a key is absent from the config.
*
* @note serviceName mirrors SystemParameters' systemName() ("xrpld") but
* is duplicated here as a literal: the telemetry module deliberately does
* not link xrpl.libxrpl.protocol, so including SystemParameters.h would
* introduce an undeclared cross-module dependency.
*/
namespace dflt {
constexpr char const* serviceName = "xrpld";
constexpr char const* endpoint = "http://localhost:4318/v1/traces";
@@ -57,10 +60,11 @@ constexpr std::uint32_t batchDelayMs = 5000u;
constexpr std::uint32_t maxQueueSize = 2048u;
} // namespace dflt
/** Derive a human-readable network type label from the numeric network ID.
@param networkId The network identifier from [network_id] config.
@return "mainnet", "testnet", "devnet", or "unknown" for other values.
*/
/**
* Derive a human-readable network type label from the numeric network ID.
* @param networkId The network identifier from [network_id] config.
* @return "mainnet", "testnet", "devnet", or "unknown" for other values.
*/
std::string
networkTypeFromId(std::uint32_t networkId)
{

View File

@@ -197,7 +197,9 @@ private:
std::vector<boost::asio::ip::address> const& secureGatewayIPs_;
/// Human-readable name for telemetry spans (e.g. "GetLedger").
/**
* Human-readable name for telemetry spans (e.g. "GetLedger").
*/
std::string_view name_;
public:

View File

@@ -1,6 +1,7 @@
#pragma once
/** Compile-time span name constants for the gRPC subsystem.
/**
* Compile-time span name constants for the gRPC subsystem.
*
* All span prefixes, operation names, and attribute keys used by gRPC
* tracing call sites are defined here. Built on the StaticStr/join()
@@ -28,19 +29,27 @@ namespace xrpl::telemetry::grpc_span {
// ===== Span prefixes =======================================================
namespace prefix {
/// "grpc" — root prefix for gRPC transport spans. The full span name is
/// formed at the call site as `grpc.<MethodName>` (see GRPCServer.cpp).
/**
* "grpc" — root prefix for gRPC transport spans. The full span name is
* formed at the call site as `grpc.<MethodName>` (see GRPCServer.cpp).
*/
inline constexpr auto grpc = makeStr("grpc");
} // namespace prefix
// ===== Attribute keys ======================================================
namespace attr {
/// "method" — gRPC method name (e.g. GetLedger).
/**
* "method" — gRPC method name (e.g. GetLedger).
*/
inline constexpr auto method = makeStr("method");
/// "grpc_role" — Domain-qualified: collides with rpc_role.
/**
* "grpc_role" — Domain-qualified: collides with rpc_role.
*/
inline constexpr auto grpcRole = makeStr("grpc_role");
/// "grpc_status" — Domain-qualified: avoids OTel reserved span status.
/**
* "grpc_status" — Domain-qualified: avoids OTel reserved span status.
*/
inline constexpr auto grpcStatus = makeStr("grpc_status");
} // namespace attr

View File

@@ -192,10 +192,11 @@ private:
void
processSession(std::shared_ptr<Session> const&, std::shared_ptr<JobQueue::Coro> coro);
/** Process an RPC request and write the reply to `output`.
@return false if the request resulted in an error response, true
otherwise. Lets the caller's enclosing span reflect the outcome.
*/
/**
* Process an RPC request and write the reply to `output`.
* @return false if the request resulted in an error response, true
* otherwise. Lets the caller's enclosing span reflect the outcome.
*/
bool
processRequest(
Port const& port,

View File

@@ -1,6 +1,7 @@
#pragma once
/** Compile-time span name constants for the RPC subsystem.
/**
* Compile-time span name constants for the RPC subsystem.
*
* All span prefixes, operation names, and attribute keys used by RPC
* tracing call sites are defined here. Built on the StaticStr/join()
@@ -116,9 +117,13 @@ namespace xrpl::telemetry::rpc_span {
// ===== Span prefixes =======================================================
namespace prefix {
/// "rpc" — root prefix for transport-level spans.
/**
* "rpc" — root prefix for transport-level spans.
*/
inline constexpr auto rpc = seg::rpc;
/// "rpc.command" — prefix for individual RPC command spans.
/**
* "rpc.command" — prefix for individual RPC command spans.
*/
inline constexpr auto command = join(seg::rpc, makeStr("command"));
} // namespace prefix
@@ -134,15 +139,25 @@ inline constexpr auto process = makeStr("process");
// ===== Attribute keys ======================================================
namespace attr {
/// "command" — RPC method name.
/**
* "command" — RPC method name.
*/
inline constexpr auto command = makeStr("command");
/// "version" — api_version per request.
/**
* "version" — api_version per request.
*/
inline constexpr auto version = makeStr("version");
/// "rpc_role" — admin|user. Domain-qualified: collides with grpc_role.
/**
* "rpc_role" — admin|user. Domain-qualified: collides with grpc_role.
*/
inline constexpr auto rpcRole = makeStr("rpc_role");
/// "rpc_status" — success|error. Domain-qualified: avoids OTel reserved span status.
/**
* "rpc_status" — success|error. Domain-qualified: avoids OTel reserved span status.
*/
inline constexpr auto rpcStatus = makeStr("rpc_status");
/// "request_payload_size" — bytes of inbound request payload.
/**
* "request_payload_size" — bytes of inbound request payload.
*/
inline constexpr auto requestPayloadSize = makeStr("request_payload_size");
} // namespace attr
@@ -154,7 +169,9 @@ using telemetry::attr_val::success;
inline constexpr auto admin = makeStr("admin");
inline constexpr auto user = makeStr("user");
inline constexpr auto unknownCommand = makeStr("unknown");
/// "invalid_json" — WS message parse failure or oversize.
/**
* "invalid_json" — WS message parse failure or oversize.
*/
inline constexpr auto invalidJson = makeStr("invalid_json");
} // namespace val