feat(telemetry): replace tracing macros with SpanGuard factory pattern

Delete TracingInstrumentation.h and replace all XRPL_TRACE_* macro
invocations with direct SpanGuard::rpcSpan() calls. SpanGuard's pimpl
design and global Telemetry accessor eliminate the need for macro
wrappers and explicit Telemetry instance passing at call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Pratik Mankawde
2026-04-17 16:47:52 +01:00
parent 025a8a344b
commit 9e4d943c69
4 changed files with 19 additions and 164 deletions

View File

@@ -119,7 +119,7 @@ endif()
# 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.
# so that SpanGuard factory methods produce real OTel spans.
# When OFF (default), all tracing code compiles to no-ops with zero overhead.
# Enable via: conan install -o telemetry=True, or cmake -Dtelemetry=ON.
option(telemetry "Enable OpenTelemetry tracing" OFF)

View File

@@ -7,7 +7,6 @@
#include <xrpld/rpc/Status.h>
#include <xrpld/rpc/detail/Handler.h>
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpld/telemetry/TracingInstrumentation.h>
#include <xrpl/basics/Log.h>
#include <xrpl/core/Job.h>
@@ -17,6 +16,9 @@
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/resource/Fees.h>
#include <xrpl/server/InfoSub.h>
#include <xrpl/server/NetworkOPs.h>
#include <xrpl/telemetry/SpanGuard.h>
#include <atomic>
#include <chrono>
@@ -158,10 +160,10 @@ template <class Object, class Method>
Status
callMethod(JsonContext& context, Method method, std::string const& name, Object& result)
{
XRPL_TRACE_RPC(context.app.getTelemetry(), "rpc.command." + name);
XRPL_TRACE_SET_ATTR("xrpl.rpc.command", name.c_str());
XRPL_TRACE_SET_ATTR("xrpl.rpc.version", static_cast<int64_t>(context.apiVersion));
XRPL_TRACE_SET_ATTR("xrpl.rpc.role", (context.role == Role::ADMIN ? "admin" : "user"));
auto span = telemetry::SpanGuard::rpcSpan("rpc.command." + name);
span.setAttribute("xrpl.rpc.command", name.c_str());
span.setAttribute("xrpl.rpc.version", static_cast<int64_t>(context.apiVersion));
span.setAttribute("xrpl.rpc.role", (context.role == Role::ADMIN ? "admin" : "user"));
static std::atomic<std::uint64_t> requestId{0};
auto& perfLog = context.app.getPerfLog();
@@ -178,15 +180,15 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object&
JLOG(context.j.debug()) << "RPC call " << name << " completed in "
<< ((end - start).count() / 1000000000.0) << "seconds";
perfLog.rpcFinish(name, curId);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "success");
span.setAttribute("xrpl.rpc.status", "success");
return ret;
}
catch (std::exception& e)
{
perfLog.rpcError(name, curId);
JLOG(context.j.info()) << "Caught throw: " << e.what();
XRPL_TRACE_EXCEPTION(e);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "error");
span.recordException(e);
span.setAttribute("xrpl.rpc.status", "error");
if (context.loadType == Resource::feeReferenceRPC)
context.loadType = Resource::feeExceptionRPC;

View File

@@ -8,7 +8,6 @@
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpld/rpc/detail/WSInfoSub.h>
#include <xrpld/rpc/json_body.h>
#include <xrpld/telemetry/TracingInstrumentation.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base64.h>
@@ -45,6 +44,7 @@
#include <xrpl/server/SimpleWriter.h>
#include <xrpl/server/WSSession.h>
#include <xrpl/server/detail/JSONRPCUtil.h>
#include <xrpl/telemetry/SpanGuard.h>
#include <boost/algorithm/string/trim.hpp>
#include <boost/asio/buffer.hpp>
@@ -418,7 +418,7 @@ ServerHandler::processSession(
std::shared_ptr<JobQueue::Coro> const& coro,
Json::Value const& jv)
{
XRPL_TRACE_RPC(app_.getTelemetry(), "rpc.ws_message");
auto span = telemetry::SpanGuard::rpcSpan("rpc.ws_message");
auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
if (is->getConsumer().disconnect(m_journal))
{
@@ -502,8 +502,8 @@ ServerHandler::processSession(
jr[jss::result] = RPC::make_error(rpcINTERNAL);
JLOG(m_journal.error()) << "Exception while processing WS: " << ex.what() << "\n"
<< "Input JSON: " << Json::Compact{Json::Value{jv}};
XRPL_TRACE_EXCEPTION(ex);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "error");
span.recordException(ex);
span.setAttribute("xrpl.rpc.status", "error");
// LCOV_EXCL_STOP
}
@@ -563,7 +563,7 @@ ServerHandler::processSession(
std::shared_ptr<Session> const& session,
std::shared_ptr<JobQueue::Coro> coro)
{
XRPL_TRACE_RPC(app_.getTelemetry(), "rpc.http_request");
auto span = telemetry::SpanGuard::rpcSpan("rpc.http_request");
processRequest(
session->port(),
@@ -615,7 +615,7 @@ ServerHandler::processRequest(
std::string_view forwardedFor,
std::string_view user)
{
XRPL_TRACE_RPC(app_.getTelemetry(), "rpc.process");
auto span = telemetry::SpanGuard::rpcSpan("rpc.process");
auto rpcJ = app_.getJournal("RPC");
Json::Value jsonOrig;
@@ -892,8 +892,8 @@ ServerHandler::processRequest(
JLOG(m_journal.error())
<< "Internal error : " << ex.what()
<< " when processing request: " << Json::Compact{Json::Value{params}};
XRPL_TRACE_EXCEPTION(ex);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "error");
span.recordException(ex);
span.setAttribute("xrpl.rpc.status", "error");
// LCOV_EXCL_STOP
}

View File

@@ -1,147 +0,0 @@
#pragma once
/** Convenience macros for instrumenting code with OpenTelemetry trace spans.
When XRPL_ENABLE_TELEMETRY is defined, the macros create SpanGuard objects
that manage span lifetime via RAII. When not defined, all macros expand to
((void)0) with zero overhead.
All span-creation macros produce a std::optional<SpanGuard> named
_xrpl_guard_. The accessor macros (XRPL_TRACE_SET_ATTR,
XRPL_TRACE_EXCEPTION) reference this variable by name, so they must
appear in the same scope after exactly one span-creation macro.
@note Only one XRPL_TRACE_* span-creation macro may appear per scope,
because they all declare a variable named _xrpl_guard_. Nested spans
across function boundaries are fine (each function has its own scope).
@note These macros must not be used in single-statement if/else without
braces. The span-creation macros expand to multiple statements that
declare variables needed by the accessor macros.
@note Thread safety: Each SpanGuard binds to the constructing thread's
OTel context stack via Scope. Do not move a guard across threads.
Usage examples:
1. Basic RPC tracing:
@code
XRPL_TRACE_RPC(app.getTelemetry(), "rpc.command." + name);
XRPL_TRACE_SET_ATTR("xrpl.rpc.command", name);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "success");
@endcode
2. Exception recording:
@code
XRPL_TRACE_RPC(telemetry, "rpc.process");
try {
doWork();
} catch (std::exception const& e) {
XRPL_TRACE_EXCEPTION(e);
XRPL_TRACE_SET_ATTR("xrpl.rpc.status", "error");
}
@endcode
3. Unconditional span:
@code
XRPL_TRACE_SPAN(telemetry, "tx.apply");
XRPL_TRACE_SET_ATTR("xrpl.tx.hash", txHash);
@endcode
*/
#ifdef XRPL_ENABLE_TELEMETRY
#include <xrpl/telemetry/SpanGuard.h>
#include <xrpl/telemetry/Telemetry.h>
#include <optional>
/** Start an unconditional span, ended when the guard goes out of scope.
@param _tel_obj_ Telemetry instance reference.
@param _span_name_ Span name string.
*/
#define XRPL_TRACE_SPAN(_tel_obj_, _span_name_) \
std::optional<::xrpl::telemetry::SpanGuard> _xrpl_guard_( \
std::in_place, (_tel_obj_).startSpan(_span_name_))
/** Start an unconditional span with a specific SpanKind.
@param _tel_obj_ Telemetry instance reference.
@param _span_name_ Span name string.
@param _span_kind_ opentelemetry::trace::SpanKind value.
*/
#define XRPL_TRACE_SPAN_KIND(_tel_obj_, _span_name_, _span_kind_) \
std::optional<::xrpl::telemetry::SpanGuard> _xrpl_guard_( \
std::in_place, (_tel_obj_).startSpan(_span_name_, _span_kind_))
/** Conditionally start a span for RPC tracing.
The span is only created if shouldTraceRpc() returns true.
@param _tel_obj_ Telemetry instance reference.
@param _span_name_ Span name string.
*/
#define XRPL_TRACE_RPC(_tel_obj_, _span_name_) \
auto& _xrpl_tel_ = (_tel_obj_); \
std::optional<::xrpl::telemetry::SpanGuard> _xrpl_guard_; \
if (_xrpl_tel_.shouldTraceRpc()) \
{ \
_xrpl_guard_.emplace(_xrpl_tel_.startSpan(_span_name_)); \
}
/** Conditionally start a span for transaction tracing.
The span is only created if shouldTraceTransactions() returns true.
@param _tel_obj_ Telemetry instance reference.
@param _span_name_ Span name string.
*/
#define XRPL_TRACE_TX(_tel_obj_, _span_name_) \
auto& _xrpl_tel_ = (_tel_obj_); \
std::optional<::xrpl::telemetry::SpanGuard> _xrpl_guard_; \
if (_xrpl_tel_.shouldTraceTransactions()) \
{ \
_xrpl_guard_.emplace(_xrpl_tel_.startSpan(_span_name_)); \
}
/** Conditionally start a span for consensus tracing.
The span is only created if shouldTraceConsensus() returns true.
@param _tel_obj_ Telemetry instance reference.
@param _span_name_ Span name string.
*/
#define XRPL_TRACE_CONSENSUS(_tel_obj_, _span_name_) \
auto& _xrpl_tel_ = (_tel_obj_); \
std::optional<::xrpl::telemetry::SpanGuard> _xrpl_guard_; \
if (_xrpl_tel_.shouldTraceConsensus()) \
{ \
_xrpl_guard_.emplace(_xrpl_tel_.startSpan(_span_name_)); \
}
/** Set a key-value attribute on the current span (if it exists).
Must be used after one of the XRPL_TRACE_* span-creation macros
in the same scope.
*/
#define XRPL_TRACE_SET_ATTR(key, value) \
do \
{ \
if (_xrpl_guard_.has_value()) \
_xrpl_guard_->setAttribute(key, value); \
} while (0)
/** Record an exception on the current span and mark it as error.
Must be used after one of the XRPL_TRACE_* span-creation macros
in the same scope.
*/
#define XRPL_TRACE_EXCEPTION(e) \
do \
{ \
if (_xrpl_guard_.has_value()) \
_xrpl_guard_->recordException(e); \
} while (0)
#else // XRPL_ENABLE_TELEMETRY not defined
#define XRPL_TRACE_SPAN(_tel_obj_, _span_name_) ((void)0)
#define XRPL_TRACE_SPAN_KIND(_tel_obj_, _span_name_, _span_kind_) ((void)0)
#define XRPL_TRACE_RPC(_tel_obj_, _span_name_) ((void)0)
#define XRPL_TRACE_TX(_tel_obj_, _span_name_) ((void)0)
#define XRPL_TRACE_CONSENSUS(_tel_obj_, _span_name_) ((void)0)
#define XRPL_TRACE_SET_ATTR(key, value) ((void)0)
#define XRPL_TRACE_EXCEPTION(e) ((void)0)
#endif // XRPL_ENABLE_TELEMETRY