mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-02 16:26:48 +00:00
360 lines
9.9 KiB
C++
360 lines
9.9 KiB
C++
/** Pimpl implementation for SpanGuard and SpanContext.
|
|
|
|
All OpenTelemetry SDK types are confined to this translation unit.
|
|
The public SpanGuard.h header contains only standard-library types
|
|
and forward-declares the Impl struct.
|
|
|
|
Static factory methods access the global Telemetry instance via
|
|
Telemetry::getInstance(), check whether the requested TraceCategory
|
|
is enabled, and return either an active guard with a real Span+Scope
|
|
or a null guard whose methods are all no-ops.
|
|
|
|
The Impl struct holds the OTel Span (shared_ptr) and Scope.
|
|
Scope is non-movable, but since Impl lives behind a unique_ptr,
|
|
SpanGuard's move constructor simply transfers the pointer — no
|
|
double-Scope issues.
|
|
|
|
@see SpanGuard (SpanGuard.h), Telemetry (Telemetry.h),
|
|
FilteringSpanProcessor (Telemetry.cpp)
|
|
*/
|
|
|
|
#ifdef XRPL_ENABLE_TELEMETRY
|
|
|
|
#include <xrpl/telemetry/SpanGuard.h>
|
|
|
|
#include <xrpl/telemetry/DiscardFlag.h>
|
|
#include <xrpl/telemetry/SpanNames.h>
|
|
#include <xrpl/telemetry/Telemetry.h>
|
|
|
|
#include <opentelemetry/context/context.h>
|
|
#include <opentelemetry/context/runtime_context.h>
|
|
#include <opentelemetry/nostd/shared_ptr.h>
|
|
#include <opentelemetry/trace/context.h>
|
|
#include <opentelemetry/trace/scope.h>
|
|
#include <opentelemetry/trace/span.h>
|
|
#include <opentelemetry/trace/span_metadata.h>
|
|
#include <opentelemetry/trace/span_startoptions.h>
|
|
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <typeinfo>
|
|
#include <utility>
|
|
|
|
namespace xrpl::telemetry {
|
|
|
|
namespace otel_trace = opentelemetry::trace;
|
|
|
|
// ===== SpanContext::Impl ===================================================
|
|
|
|
struct SpanContext::Impl
|
|
{
|
|
opentelemetry::context::Context ctx;
|
|
|
|
explicit Impl(opentelemetry::context::Context c) : ctx(std::move(c))
|
|
{
|
|
}
|
|
};
|
|
|
|
SpanContext::SpanContext(std::shared_ptr<Impl> impl) : impl_(std::move(impl))
|
|
{
|
|
}
|
|
|
|
bool
|
|
SpanContext::isValid() const
|
|
{
|
|
return impl_ != nullptr;
|
|
}
|
|
|
|
// ===== SpanGuard::Impl ====================================================
|
|
|
|
struct SpanGuard::Impl
|
|
{
|
|
/** The OTel span being guarded. Set to nullptr after discard(). */
|
|
opentelemetry::nostd::shared_ptr<otel_trace::Span> span;
|
|
|
|
/** Scope that activates span on the current thread's context stack. */
|
|
otel_trace::Scope scope;
|
|
|
|
explicit Impl(opentelemetry::nostd::shared_ptr<otel_trace::Span> s)
|
|
: span(std::move(s)), scope(span)
|
|
{
|
|
}
|
|
|
|
~Impl()
|
|
{
|
|
if (span)
|
|
span->End();
|
|
}
|
|
|
|
Impl(Impl const&) = delete;
|
|
Impl&
|
|
operator=(Impl const&) = delete;
|
|
Impl(Impl&&) = delete;
|
|
Impl&
|
|
operator=(Impl&&) = delete;
|
|
};
|
|
|
|
// ===== SpanGuard core lifecycle ============================================
|
|
|
|
SpanGuard::SpanGuard() = default;
|
|
SpanGuard::~SpanGuard() = default;
|
|
SpanGuard::SpanGuard(SpanGuard&&) noexcept = default;
|
|
|
|
SpanGuard::SpanGuard(std::unique_ptr<Impl> impl) : impl_(std::move(impl))
|
|
{
|
|
}
|
|
|
|
SpanGuard::
|
|
operator bool() const
|
|
{
|
|
return impl_ != nullptr;
|
|
}
|
|
|
|
// ===== Static factory methods ==============================================
|
|
|
|
/** Check whether the given TraceCategory is enabled on the Telemetry instance.
|
|
@return true if the category's shouldTrace*() flag is on.
|
|
*/
|
|
static bool
|
|
isCategoryEnabled(Telemetry const& tel, TraceCategory cat)
|
|
{
|
|
switch (cat)
|
|
{
|
|
case TraceCategory::Rpc:
|
|
return tel.shouldTraceRpc();
|
|
case TraceCategory::Transactions:
|
|
return tel.shouldTraceTransactions();
|
|
case TraceCategory::Consensus:
|
|
return tel.shouldTraceConsensus();
|
|
case TraceCategory::Peer:
|
|
return tel.shouldTracePeer();
|
|
case TraceCategory::Ledger:
|
|
return tel.shouldTraceLedger();
|
|
}
|
|
return false; // unreachable, silences compiler warning
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Map a TraceCategory to an OTel SpanKind so Tempo's service-graph /
|
|
// RED metrics see the correct direction. RPC spans are emitted at the
|
|
// server entry point (handler dispatch), Peer spans at inbound-message
|
|
// receipt. Transactions / Consensus / Ledger are internal processing
|
|
// and keep the default kInternal.
|
|
otel_trace::SpanKind
|
|
categoryToSpanKind(TraceCategory cat)
|
|
{
|
|
switch (cat)
|
|
{
|
|
case TraceCategory::Rpc:
|
|
return otel_trace::SpanKind::kServer;
|
|
case TraceCategory::Peer:
|
|
return otel_trace::SpanKind::kConsumer;
|
|
case TraceCategory::Transactions:
|
|
case TraceCategory::Consensus:
|
|
case TraceCategory::Ledger:
|
|
return otel_trace::SpanKind::kInternal;
|
|
}
|
|
return otel_trace::SpanKind::kInternal; // unreachable
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SpanGuard
|
|
SpanGuard::span(TraceCategory cat, std::string_view prefix, std::string_view name)
|
|
{
|
|
auto* tel = Telemetry::getInstance();
|
|
if ((tel == nullptr) || !tel->isEnabled() || !isCategoryEnabled(*tel, cat))
|
|
return {};
|
|
auto fullName = std::string(prefix) + "." + std::string(name);
|
|
return SpanGuard(std::make_unique<Impl>(tel->startSpan(fullName, categoryToSpanKind(cat))));
|
|
}
|
|
|
|
// ===== Child / linked span creation ========================================
|
|
|
|
SpanGuard
|
|
SpanGuard::childSpan(std::string_view name) const
|
|
{
|
|
if (!impl_)
|
|
return {};
|
|
auto* tel = Telemetry::getInstance();
|
|
if ((tel == nullptr) || !tel->isEnabled())
|
|
return {};
|
|
auto ctx = opentelemetry::context::RuntimeContext::GetCurrent();
|
|
return SpanGuard(std::make_unique<Impl>(tel->startSpan(name, ctx)));
|
|
}
|
|
|
|
SpanGuard
|
|
SpanGuard::childSpan(std::string_view name, SpanContext const& parentCtx)
|
|
{
|
|
if (!parentCtx.isValid())
|
|
return {};
|
|
auto* tel = Telemetry::getInstance();
|
|
if ((tel == nullptr) || !tel->isEnabled())
|
|
return {};
|
|
return SpanGuard(std::make_unique<Impl>(tel->startSpan(name, parentCtx.impl_->ctx)));
|
|
}
|
|
|
|
SpanGuard
|
|
SpanGuard::linkedSpan(std::string_view name) const
|
|
{
|
|
if (!impl_)
|
|
return {};
|
|
auto* tel = Telemetry::getInstance();
|
|
if ((tel == nullptr) || !tel->isEnabled())
|
|
return {};
|
|
|
|
auto tracer = tel->getTracer("xrpld");
|
|
auto spanCtx = impl_->span->GetContext();
|
|
|
|
// Mark as root span so it starts a new trace sub-tree rather than
|
|
// inheriting the current thread's active span as parent.
|
|
otel_trace::StartSpanOptions opts;
|
|
opentelemetry::context::Context rootCtx;
|
|
rootCtx = rootCtx.SetValue(otel_trace::kIsRootSpanKey, true);
|
|
opts.parent = rootCtx;
|
|
|
|
return SpanGuard(
|
|
std::make_unique<Impl>(tracer->StartSpan(
|
|
std::string(name),
|
|
{},
|
|
{{spanCtx, {{std::string(attr::linkType), std::string(attr_val::followsFrom)}}}},
|
|
opts)));
|
|
}
|
|
|
|
SpanGuard
|
|
SpanGuard::linkedSpan(std::string_view name, SpanContext const& linkCtx)
|
|
{
|
|
if (!linkCtx.isValid())
|
|
return {};
|
|
auto* tel = Telemetry::getInstance();
|
|
if ((tel == nullptr) || !tel->isEnabled())
|
|
return {};
|
|
|
|
auto tracer = tel->getTracer("xrpld");
|
|
|
|
// Extract the span from the captured context to get its SpanContext.
|
|
auto linkSpan = otel_trace::GetSpan(linkCtx.impl_->ctx);
|
|
if (!linkSpan || !linkSpan->GetContext().IsValid())
|
|
return {};
|
|
|
|
// Mark as root span so it starts a new trace sub-tree rather than
|
|
// inheriting the current thread's active span as parent.
|
|
otel_trace::StartSpanOptions opts;
|
|
opentelemetry::context::Context rootCtx;
|
|
rootCtx = rootCtx.SetValue(otel_trace::kIsRootSpanKey, true);
|
|
opts.parent = rootCtx;
|
|
|
|
return SpanGuard(
|
|
std::make_unique<Impl>(tracer->StartSpan(
|
|
std::string(name),
|
|
{},
|
|
{{linkSpan->GetContext(),
|
|
{{std::string(attr::linkType), std::string(attr_val::followsFrom)}}}},
|
|
opts)));
|
|
}
|
|
|
|
// ===== Context capture =====================================================
|
|
|
|
SpanContext
|
|
SpanGuard::captureContext() const
|
|
{
|
|
if (!impl_)
|
|
return {};
|
|
auto ctx = opentelemetry::context::RuntimeContext::GetCurrent();
|
|
return SpanContext(std::make_shared<SpanContext::Impl>(ctx));
|
|
}
|
|
|
|
// ===== Attribute setters ===================================================
|
|
|
|
void
|
|
SpanGuard::setAttribute(std::string_view key, std::string_view value)
|
|
{
|
|
if (impl_)
|
|
{
|
|
impl_->span->SetAttribute(
|
|
opentelemetry::nostd::string_view(key.data(), key.size()),
|
|
opentelemetry::nostd::string_view(value.data(), value.size()));
|
|
}
|
|
}
|
|
|
|
void
|
|
SpanGuard::setAttribute(std::string_view key, char const* value)
|
|
{
|
|
setAttribute(key, std::string_view(value));
|
|
}
|
|
|
|
void
|
|
SpanGuard::setAttribute(std::string_view key, std::int64_t value)
|
|
{
|
|
if (impl_)
|
|
impl_->span->SetAttribute(opentelemetry::nostd::string_view(key.data(), key.size()), value);
|
|
}
|
|
|
|
void
|
|
SpanGuard::setAttribute(std::string_view key, double value)
|
|
{
|
|
if (impl_)
|
|
impl_->span->SetAttribute(opentelemetry::nostd::string_view(key.data(), key.size()), value);
|
|
}
|
|
|
|
void
|
|
SpanGuard::setAttribute(std::string_view key, bool value)
|
|
{
|
|
if (impl_)
|
|
impl_->span->SetAttribute(opentelemetry::nostd::string_view(key.data(), key.size()), value);
|
|
}
|
|
|
|
// ===== Status / events =====================================================
|
|
|
|
void
|
|
SpanGuard::setOk()
|
|
{
|
|
if (impl_)
|
|
impl_->span->SetStatus(otel_trace::StatusCode::kOk);
|
|
}
|
|
|
|
void
|
|
SpanGuard::setError(std::string_view description)
|
|
{
|
|
if (impl_)
|
|
impl_->span->SetStatus(otel_trace::StatusCode::kError, std::string(description));
|
|
}
|
|
|
|
void
|
|
SpanGuard::addEvent(std::string_view name)
|
|
{
|
|
if (impl_)
|
|
impl_->span->AddEvent(std::string(name));
|
|
}
|
|
|
|
void
|
|
SpanGuard::recordException(std::exception const& e)
|
|
{
|
|
if (!impl_)
|
|
return;
|
|
impl_->span->AddEvent(
|
|
"exception",
|
|
{{"exception.type", typeid(e).name()}, {"exception.message", std::string(e.what())}});
|
|
impl_->span->SetStatus(otel_trace::StatusCode::kError, e.what());
|
|
}
|
|
|
|
void
|
|
SpanGuard::discard()
|
|
{
|
|
if (impl_)
|
|
{
|
|
gTlDiscardCurrentSpan = true;
|
|
impl_->span->End();
|
|
impl_->span = nullptr; // prevent ~Impl from calling End() again
|
|
impl_.reset();
|
|
}
|
|
}
|
|
|
|
} // namespace xrpl::telemetry
|
|
|
|
#endif // XRPL_ENABLE_TELEMETRY
|