diff --git a/include/xrpl/telemetry/SpanGuard.h b/include/xrpl/telemetry/SpanGuard.h index d235f57ece..8d2aa15dab 100644 --- a/include/xrpl/telemetry/SpanGuard.h +++ b/include/xrpl/telemetry/SpanGuard.h @@ -33,7 +33,8 @@ * | + freshRoot(cat, prefix, name) [static] | * | + childSpan(name) : SpanGuard | * | + linkedSpan(name) : SpanGuard | - * | + captureContext() : SpanContext | + * | + spanContext() : SpanContext | + * | + threadLocalContext() [static] | * | + setAttribute(key, value) | * | + setOk() / setError(desc) | * | + addEvent(name) | @@ -89,10 +90,10 @@ * #include * using namespace xrpl::telemetry; * - * // Thread A: create span and capture context + * // Thread A: create span and capture its own context as parent * auto span = SpanGuard::span( * TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::process); - * auto ctx = span.captureContext(); + * auto ctx = span.spanContext(); * * // Thread B: create child with captured context * auto child = SpanGuard::childSpan(rpc_span::op::process, ctx); @@ -155,8 +156,9 @@ * span (no Scope), so it never binds to a thread-local context stack * and may be moved to and destroyed on any thread. To make a span the * ambient parent for child spans on a thread, wrap it in a - * ScopedSpanGuard. Use captureContext() to propagate the trace - * context across threads explicitly. + * ScopedSpanGuard. Use spanContext() to propagate this span's own + * context across threads explicitly, or threadLocalContext() to + * snapshot the thread's currently-active context. * * @note Move semantics: Move construction and move assignment both * transfer ownership of the pimpl pointer. There is no Scope to @@ -220,8 +222,10 @@ using EventAttribute = std::pair; * Opaque wrapper for an OTel context snapshot. * * Used to propagate trace context across threads. Created by - * SpanGuard::captureContext(), consumed by SpanGuard::childSpan() - * or SpanGuard::linkedSpan() with an explicit parent/link context. + * SpanGuard::spanContext() (the guard's own span) or + * SpanGuard::threadLocalContext() (the thread's current context), + * consumed by SpanGuard::childSpan() or SpanGuard::linkedSpan() + * with an explicit parent/link context. */ class SpanContext { @@ -342,7 +346,7 @@ public: /** * Create a child span parented to an explicit captured context. * @param name Span name for the child. - * @param parentCtx Context captured via captureContext(). + * @param parentCtx Context captured via spanContext(). * @return A new guard, or null if parentCtx is invalid. */ [[nodiscard]] static SpanGuard @@ -418,16 +422,34 @@ public: // --- Context capture ----------------------------------------------- /** - * Snapshot the current thread's OTel context for cross-thread use. - * @return An opaque SpanContext, or an invalid one if null guard. + * Return a SpanContext referring to THIS guard's own span, for use + * as an explicit parent in childSpan(name, ctx) across threads. + * Independent of the thread's active span (a SpanGuard is unscoped). + * Returns an invalid context if this guard is null. + * @return A SpanContext holding this guard's own span. + * @note This is NOT the thread's ambient context — use + * threadLocalContext() for that. */ [[nodiscard]] SpanContext - captureContext() const; + spanContext() const; + + /** + * Snapshot the calling thread's CURRENTLY-ACTIVE OTel context (the + * ambient span on this thread's context stack), for cross-thread + * propagation. This is the OpenTelemetry "capture current context" + * operation: capture -> pass to another thread -> childSpan(name, + * ctx). Unlike spanContext() (which refers to a specific guard's own + * span), this reflects whatever span is active on THIS thread right + * now, or an invalid context if none is active. + * @return A SpanContext holding the thread's current context. + */ + [[nodiscard]] static SpanContext + threadLocalContext(); /** * Extract raw trace context bytes from this span for propagation. * - * Unlike captureContext() which captures the thread-local runtime + * Unlike threadLocalContext() which captures the thread-local runtime * context, this method reads the span's own SpanContext directly. * Safe to call from any thread that holds a reference to this guard. * @@ -568,7 +590,7 @@ public: * | + linkedSpan(name) : ScopedSpanGuard | * | + operator SpanGuard() && (handoff) | * | + setAttribute / setOk / setError / ... | - * | + captureContext() / discard() | + * | + spanContext() / discard() | * | + operator bool() | * +--------------------------------------------+ * | hides (pimpl) @@ -674,7 +696,7 @@ public: * Create a scoped child span parented to an explicit captured * context and activate it on this thread. * @param name Span name for the child. - * @param parentCtx Context captured via captureContext(). + * @param parentCtx Context captured via spanContext(). * @return A new scoped guard, or a null one if parentCtx is invalid. */ [[nodiscard]] static ScopedSpanGuard @@ -712,11 +734,16 @@ public: // --- Context capture ----------------------------------------------- /** - * Snapshot the current thread's OTel context for cross-thread use. - * @return An opaque SpanContext, or an invalid one if null guard. + * Return a SpanContext referring to the owned guard's own span, for + * use as an explicit parent in childSpan(name, ctx) across threads. + * Forwards to the owned SpanGuard's spanContext(). Returns an invalid + * context if this guard is null. + * @return A SpanContext holding the owned guard's own span. + * @note This is NOT the thread's ambient context — use + * SpanGuard::threadLocalContext() for that. */ [[nodiscard]] SpanContext - captureContext() const; + spanContext() const; // --- Forwarding methods (delegate to the owned SpanGuard) ---------- @@ -868,7 +895,7 @@ public: } [[nodiscard]] SpanContext - captureContext() const + spanContext() const { return {}; } @@ -884,6 +911,12 @@ public: } // NOLINTEND(readability-convert-member-functions-to-static) + [[nodiscard]] static SpanContext + threadLocalContext() + { + return {}; + } + void setAttribute(std::string_view, std::string_view) { @@ -986,7 +1019,7 @@ public: } [[nodiscard]] SpanContext - captureContext() const + spanContext() const { return {}; } diff --git a/include/xrpl/telemetry/Telemetry.h b/include/xrpl/telemetry/Telemetry.h index df6d6db2ea..0293407ea9 100644 --- a/include/xrpl/telemetry/Telemetry.h +++ b/include/xrpl/telemetry/Telemetry.h @@ -74,8 +74,8 @@ * * 4. Cross-thread context propagation: * @code - * // Thread A: capture the active context while span is in scope - * auto ctx = parentGuard.captureContext(); + * // Thread A: take a handle to the parent span's own context + * auto ctx = parentGuard.spanContext(); * * // Thread B: create child span with explicit parent * auto child = SpanGuard::childSpan(rpc_span::op::process, ctx); diff --git a/src/libxrpl/telemetry/SpanGuard.cpp b/src/libxrpl/telemetry/SpanGuard.cpp index 045a86163d..7467610ede 100644 --- a/src/libxrpl/telemetry/SpanGuard.cpp +++ b/src/libxrpl/telemetry/SpanGuard.cpp @@ -399,20 +399,30 @@ SpanGuard::hashSpan( // ===== Context capture ===================================================== SpanContext -SpanGuard::captureContext() const +SpanGuard::spanContext() const { if (!impl_ || !impl_->span) return {}; - // Capture THIS guard's own span, not the thread's ambient span. A + // Refer to THIS guard's own span, not the thread's ambient span. A // SpanGuard is unscoped (never pushed onto the thread-local stack), so // RuntimeContext::GetCurrent() would return an unrelated ambient span. - // Building the context from impl_->span makes captureContext() correct + // Building the context from impl_->span makes spanContext() correct // for every caller regardless of scoping, and lets childSpan(name, ctx) // parent explicitly to this span across threads. auto ctx = opentelemetry::context::Context{}.SetValue(otel_trace::kSpanKey, impl_->span); return SpanContext(std::make_shared(std::move(ctx))); } +SpanContext +SpanGuard::threadLocalContext() +{ + // Snapshot whatever span is currently active on THIS thread's context + // 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(std::move(ctx))); +} + TraceBytes SpanGuard::getTraceBytes() const { @@ -679,9 +689,9 @@ operator SpanGuard() && // ===== ScopedSpanGuard context capture ===================================== SpanContext -ScopedSpanGuard::captureContext() const +ScopedSpanGuard::spanContext() const { - return impl_->guard.captureContext(); + return impl_->guard.spanContext(); } // ===== ScopedSpanGuard forwarding methods ==================================