diff --git a/include/xrpl/telemetry/SpanGuard.h b/include/xrpl/telemetry/SpanGuard.h index 8ff955615c..6718052219 100644 --- a/include/xrpl/telemetry/SpanGuard.h +++ b/include/xrpl/telemetry/SpanGuard.h @@ -44,21 +44,20 @@ 1. Basic RPC tracing (factory method with category): @code - // Define prefix at class level: - static constexpr std::string_view spanPrefix_ = "rpc.command"; + #include - // At the call site: + // At the call site (constants from RpcSpanNames.h): auto span = SpanGuard::span( - TraceCategory::Rpc, spanPrefix_, "submit"); - span.setAttribute("xrpl.rpc.command", "submit"); - span.setAttribute("xrpl.rpc.status", "success"); + TraceCategory::Rpc, rpc_span::prefix::command, "submit"); + span.setAttribute(rpc_span::attr::command, "submit"); + span.setAttribute(rpc_span::attr::status, rpc_span::val::success); // span ended automatically on scope exit @endcode 2. Error recording: @code auto span = SpanGuard::span( - TraceCategory::Rpc, "rpc.command", "submit"); + TraceCategory::Rpc, rpc_span::prefix::command, "submit"); try { doWork(); span.setOk(); @@ -71,7 +70,7 @@ @code // Thread A: create span and capture context auto span = SpanGuard::span( - TraceCategory::Consensus, "consensus", "round"); + TraceCategory::Consensus, seg::consensus, "round"); auto ctx = span.captureContext(); // Thread B: create child with captured context @@ -81,17 +80,17 @@ 4. Conditional check (rarely needed — methods are no-ops on null): @code auto span = SpanGuard::span( - TraceCategory::Rpc, "rpc", "request"); + TraceCategory::Rpc, rpc_span::prefix::rpc, "request"); if (span) { // expensive attribute computation only when active - span.setAttribute("xrpl.rpc.payload_size", computeSize()); + span.setAttribute(rpc_span::attr::payloadSize, computeSize()); } @endcode 5. Tail-based filtering via discard(): @code auto span = SpanGuard::span( - TraceCategory::Transactions, "tx", "process"); + TraceCategory::Transactions, seg::tx, "process"); auto result = preflight(tx); if (result != tesSUCCESS) { span.discard(); // drop span, never exported diff --git a/include/xrpl/telemetry/SpanNames.h b/include/xrpl/telemetry/SpanNames.h new file mode 100644 index 0000000000..0fde9a18c1 --- /dev/null +++ b/include/xrpl/telemetry/SpanNames.h @@ -0,0 +1,114 @@ +#pragma once + +/** Compile-time string concatenation utility and shared telemetry constants. + * + * Provides StaticStr — 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 + * 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 + * concatenation support. boost::static_string is not constexpr. + * StaticStr exists specifically for compile-time dot-join composition. + * + * Naming conventions follow OpenTelemetry semantic conventions: + * - Attribute keys: "xrpl.." + * - Span prefixes: "[.]" + */ + +#include +#include + +namespace xrpl { +namespace telemetry { + +// ===== Compile-time string utility ========================================= + +/// Fixed-size character buffer for compile-time string operations. +/// Implicitly converts to std::string_view at zero cost. +template +struct StaticStr +{ + char data[N + 1]{}; + static constexpr std::size_t size = N; + + constexpr StaticStr() = default; + + constexpr explicit StaticStr(char const (&str)[N + 1]) + { + for (std::size_t i = 0; i <= N; ++i) + data[i] = str[i]; + } + + constexpr + operator std::string_view() const noexcept + { + return {data, N}; + } +}; + +/// Deduction guide: StaticStr from string literal. +template +StaticStr(char const (&)[N]) -> StaticStr; + +/// Create a StaticStr from a string literal. +template +constexpr auto +makeStr(char const (&str)[N]) +{ + return StaticStr(str); +} + +/// Concatenate two StaticStr values with a dot separator. +template +constexpr auto +join(StaticStr const& lhs, StaticStr const& rhs) +{ + constexpr std::size_t len = A + 1 + B; // lhs + '.' + rhs + StaticStr result; + std::size_t pos = 0; + for (std::size_t i = 0; i < A; ++i) + result.data[pos++] = lhs.data[i]; + result.data[pos++] = '.'; + for (std::size_t i = 0; i < B; ++i) + result.data[pos++] = rhs.data[i]; + result.data[pos] = '\0'; + return result; +} + +// ===== Shared root segments ================================================ + +namespace seg { +inline constexpr auto xrpl = makeStr("xrpl"); +inline constexpr auto rpc = makeStr("rpc"); +inline constexpr auto tx = makeStr("tx"); +inline constexpr auto consensus = makeStr("consensus"); +inline constexpr auto peer = makeStr("peer"); +inline constexpr auto ledger = makeStr("ledger"); +inline constexpr auto network = makeStr("network"); +inline constexpr auto link = makeStr("link"); +} // namespace seg + +// ===== Shared attribute keys (used across modules) ========================= + +namespace attr { +inline constexpr auto networkId = join(join(seg::xrpl, seg::network), makeStr("id")); +inline constexpr auto networkType = join(join(seg::xrpl, seg::network), makeStr("type")); +inline constexpr auto linkType = join(join(seg::xrpl, seg::link), makeStr("type")); +} // namespace attr + +// ===== Shared attribute values ============================================= + +namespace attr_val { +inline constexpr auto success = makeStr("success"); +inline constexpr auto error = makeStr("error"); +inline constexpr auto followsFrom = makeStr("follows_from"); +} // namespace attr_val + +} // namespace telemetry +} // namespace xrpl diff --git a/src/libxrpl/telemetry/SpanGuard.cpp b/src/libxrpl/telemetry/SpanGuard.cpp index 22c210c7c5..4332f0f7b5 100644 --- a/src/libxrpl/telemetry/SpanGuard.cpp +++ b/src/libxrpl/telemetry/SpanGuard.cpp @@ -20,9 +20,9 @@ #ifdef XRPL_ENABLE_TELEMETRY -#include - #include +#include +#include #include #include @@ -188,7 +188,10 @@ SpanGuard::linkedSpan(std::string_view name) const return SpanGuard( std::make_unique(tracer->StartSpan( - std::string(name), {}, {{spanCtx, {{"xrpl.link.type", "follows_from"}}}}, opts))); + std::string(name), + {}, + {{spanCtx, {{std::string(attr::linkType), std::string(attr_val::followsFrom)}}}}, + opts))); } SpanGuard @@ -218,7 +221,8 @@ SpanGuard::linkedSpan(std::string_view name, SpanContext const& linkCtx) std::make_unique(tracer->StartSpan( std::string(name), {}, - {{linkSpan->GetContext(), {{"xrpl.link.type", "follows_from"}}}}, + {{linkSpan->GetContext(), + {{std::string(attr::linkType), std::string(attr_val::followsFrom)}}}}, opts))); } diff --git a/src/libxrpl/telemetry/Telemetry.cpp b/src/libxrpl/telemetry/Telemetry.cpp index 6a7a42de8d..1aba913b25 100644 --- a/src/libxrpl/telemetry/Telemetry.cpp +++ b/src/libxrpl/telemetry/Telemetry.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -279,8 +280,8 @@ public: {resource::SemanticConventions::kServiceName, setup_.serviceName}, {resource::SemanticConventions::kServiceVersion, setup_.serviceVersion}, {resource::SemanticConventions::kServiceInstanceId, setup_.serviceInstanceId}, - {"xrpl.network.id", static_cast(setup_.networkId)}, - {"xrpl.network.type", setup_.networkType}, + {std::string(attr::networkId), static_cast(setup_.networkId)}, + {std::string(attr::networkType), setup_.networkType}, }); // Configure sampler diff --git a/src/xrpld/rpc/detail/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp index 3ee06b8354..19d4e8a130 100644 --- a/src/xrpld/rpc/detail/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -162,10 +163,13 @@ template Status callMethod(JsonContext& context, Method method, std::string const& name, Object& result) { - auto span = SpanGuard::span(TraceCategory::Rpc, "rpc.command", name); - span.setAttribute("xrpl.rpc.command", name.c_str()); - span.setAttribute("xrpl.rpc.version", static_cast(context.apiVersion)); - span.setAttribute("xrpl.rpc.role", (context.role == Role::ADMIN ? "admin" : "user")); + auto span = SpanGuard::span(TraceCategory::Rpc, rpc_span::prefix::command, name); + span.setAttribute(rpc_span::attr::command, name.c_str()); + span.setAttribute(rpc_span::attr::version, static_cast(context.apiVersion)); + span.setAttribute( + rpc_span::attr::role, + context.role == Role::ADMIN ? std::string_view(rpc_span::val::admin) + : std::string_view(rpc_span::val::user)); static std::atomic requestId{0}; auto& perfLog = context.app.getPerfLog(); @@ -182,7 +186,7 @@ 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); - span.setAttribute("xrpl.rpc.status", "success"); + span.setAttribute(rpc_span::attr::status, rpc_span::val::success); return ret; } catch (std::exception& e) @@ -190,7 +194,7 @@ callMethod(JsonContext& context, Method method, std::string const& name, Object& perfLog.rpcError(name, curId); JLOG(context.j.info()) << "Caught throw: " << e.what(); span.recordException(e); - span.setAttribute("xrpl.rpc.status", "error"); + span.setAttribute(rpc_span::attr::status, rpc_span::val::error); if (context.loadType == Resource::feeReferenceRPC) context.loadType = Resource::feeExceptionRPC; diff --git a/src/xrpld/rpc/detail/RpcSpanNames.h b/src/xrpld/rpc/detail/RpcSpanNames.h new file mode 100644 index 0000000000..a10fd1af3e --- /dev/null +++ b/src/xrpld/rpc/detail/RpcSpanNames.h @@ -0,0 +1,72 @@ +#pragma once + +/** 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() + * primitives from . + * + * Usage: + * @code + * #include + * using namespace telemetry; + * + * auto span = SpanGuard::span( + * TraceCategory::Rpc, rpc_span::prefix::command, "submit"); + * span.setAttribute(rpc_span::attr::command, "submit"); + * span.setAttribute(rpc_span::attr::status, rpc_span::val::success); + * @endcode + */ + +#include + +namespace xrpl { +namespace telemetry { +namespace rpc_span { + +// ===== Span prefixes ======================================================= + +namespace prefix { +/// "rpc" — root prefix for transport-level spans. +inline constexpr auto rpc = seg::rpc; +/// "rpc.command" — prefix for individual RPC command spans. +inline constexpr auto command = join(seg::rpc, makeStr("command")); +} // namespace prefix + +// ===== Span operation suffixes ============================================= + +namespace op { +inline constexpr auto wsMessage = makeStr("ws_message"); +inline constexpr auto httpRequest = makeStr("http_request"); +inline constexpr auto process = makeStr("process"); +} // namespace op + +// ===== Attribute keys ====================================================== + +namespace attr { +inline constexpr auto xrplRpc = join(seg::xrpl, seg::rpc); + +/// "xrpl.rpc.command" +inline constexpr auto command = join(xrplRpc, makeStr("command")); +/// "xrpl.rpc.version" +inline constexpr auto version = join(xrplRpc, makeStr("version")); +/// "xrpl.rpc.role" +inline constexpr auto role = join(xrplRpc, makeStr("role")); +/// "xrpl.rpc.status" +inline constexpr auto status = join(xrplRpc, makeStr("status")); +/// "xrpl.rpc.payload_size" +inline constexpr auto payloadSize = join(xrplRpc, makeStr("payload_size")); +} // namespace attr + +// ===== Attribute values ==================================================== + +namespace val { +using telemetry::attr_val::error; +using telemetry::attr_val::success; +inline constexpr auto admin = makeStr("admin"); +inline constexpr auto user = makeStr("user"); +} // namespace val + +} // namespace rpc_span +} // namespace telemetry +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 041002cd7e..73bae08a30 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -419,7 +420,7 @@ ServerHandler::processSession( std::shared_ptr const& coro, Json::Value const& jv) { - auto span = SpanGuard::span(TraceCategory::Rpc, "rpc", "ws_message"); + auto span = SpanGuard::span(TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::wsMessage); auto is = std::static_pointer_cast(session->appDefined); if (is->getConsumer().disconnect(m_journal)) { @@ -504,7 +505,7 @@ ServerHandler::processSession( JLOG(m_journal.error()) << "Exception while processing WS: " << ex.what() << "\n" << "Input JSON: " << Json::Compact{Json::Value{jv}}; span.recordException(ex); - span.setAttribute("xrpl.rpc.status", "error"); + span.setAttribute(rpc_span::attr::status, rpc_span::val::error); // LCOV_EXCL_STOP } @@ -564,7 +565,8 @@ ServerHandler::processSession( std::shared_ptr const& session, std::shared_ptr coro) { - auto span = SpanGuard::span(TraceCategory::Rpc, "rpc", "http_request"); + auto span = + SpanGuard::span(TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::httpRequest); processRequest( session->port(), @@ -616,7 +618,7 @@ ServerHandler::processRequest( std::string_view forwardedFor, std::string_view user) { - auto span = SpanGuard::span(TraceCategory::Rpc, "rpc", "process"); + auto span = SpanGuard::span(TraceCategory::Rpc, rpc_span::prefix::rpc, rpc_span::op::process); auto rpcJ = app_.getJournal("RPC"); Json::Value jsonOrig; @@ -894,7 +896,7 @@ ServerHandler::processRequest( << "Internal error : " << ex.what() << " when processing request: " << Json::Compact{Json::Value{params}}; span.recordException(ex); - span.setAttribute("xrpl.rpc.status", "error"); + span.setAttribute(rpc_span::attr::status, rpc_span::val::error); // LCOV_EXCL_STOP }