Merge branch 'pratik/otel-phase7-native-metrics' into pratik/otel-phase8-log-correlation

This commit is contained in:
Pratik Mankawde
2026-07-24 16:14:30 +01:00
13 changed files with 223 additions and 59 deletions

View File

@@ -163,14 +163,14 @@ or, for the apply pipeline: `{resource.service.name="xrpld" && name=~"tx.preflig
Controlled by `trace_transactions=1` in `[telemetry]` config.
| Span Name | Parent | Source File | Description |
| ------------------ | ------------- | ----------- | --------------------------------------------------- |
| `txq.enqueue` | `tx.process` | TxQ.cpp | Enqueue decision when a tx is submitted |
| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct apply attempt that bypasses the queue |
| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of an account's queued txs |
| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drains the queue) |
| `txq.accept_tx` | `txq.accept` | TxQ.cpp | Per-queued-transaction apply inside the accept loop |
| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup of expired queue entries |
| Span Name | Parent | Source File | Description |
| ------------------ | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `txq.enqueue` | `tx.process` (submission path; root on open-ledger rebuild) | TxQ.cpp | Queue admission decision (apply/queue/reject). Parents to `tx.process` via explicit context on submit; correlates via `current_ledger_seq` on all paths |
| `txq.apply_direct` | `txq.enqueue` | TxQ.cpp | Direct apply attempt that bypasses the queue |
| `txq.batch_clear` | `txq.enqueue` | TxQ.cpp | Batch clear of an account's queued txs |
| `txq.accept` | — | TxQ.cpp | Ledger-close accept loop (drains the queue) |
| `txq.accept_tx` | `txq.accept` | TxQ.cpp | Per-queued-transaction apply inside the accept loop |
| `txq.cleanup` | — | TxQ.cpp | Post-close cleanup of expired queue entries |
**Where to find**: Tempo → TraceQL: `{resource.service.name="xrpld" && name=~"txq.*"}`
@@ -310,42 +310,47 @@ aggregation. Per the 2026-05-13 naming redesign, span-attribute keys use the
#### Transaction Attributes
| Attribute | Type | Set On | Description |
| -------------- | ------- | ------------------------------------------------------------ | --------------------------------------------------------------------- |
| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) |
| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed |
| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` |
| `tx_type` | string | `tx.process`, `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) |
| `fee` | int64 | `tx.process` | Transaction fee in drops |
| `sequence` | int64 | `tx.process` | Transaction sequence number |
| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) |
| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) |
| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) |
| `peer_version` | string | `tx.receive` | Peer protocol version string |
| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` |
| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) |
| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger |
| Attribute | Type | Set On | Description |
| --------------------- | ------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `tx_hash` | string | `tx.process`, `tx.receive` | Transaction hash (hex-encoded) |
| `local` | boolean | `tx.process` | `true` if locally submitted, `false` if peer-relayed |
| `path` | string | `tx.process` | Submission path: `"sync"` or `"async"` |
| `tx_type` | string | `tx.process`, `tx.preflight`, `tx.preclaim`, `tx.transactor` | Transaction type name (e.g., `Payment`) |
| `fee` | int64 | `tx.process` | Transaction fee in drops |
| `sequence` | int64 | `tx.process` | Transaction sequence number |
| `suppressed` | boolean | `tx.receive` | `true` if transaction was suppressed (duplicate) |
| `tx_status` | string | `tx.receive` | Transaction status (e.g., `"known_bad"`) |
| `peer_id` | int64 | `tx.receive` | Peer identifier (also set on peer spans) |
| `peer_version` | string | `tx.receive` | Peer protocol version string |
| `stage` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Apply-pipeline stage: `preflight`, `preclaim`, or `apply` |
| `ter_result` | string | `tx.preflight`, `tx.preclaim`, `tx.transactor` | Engine result token for that stage (e.g., `tesSUCCESS`, `terPRE_SEQ`) |
| `applied` | boolean | `tx.transactor` | `true` if the transaction was applied to the ledger |
| `current_ledger_seq` | int64 | `tx.process`, `tx.receive`, `tx.preclaim`, `tx.transactor` | Seq of the ledger being worked on (open/in-flight, not established) — joins the txID-keyed spans to the ledger trace |
| `current_ledger_hash` | string | `tx.preclaim`, `tx.transactor` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path). View-bearing stages only; `tx.preflight` omits both |
**Tempo query**: `{span.tx_hash="<hash>"}` to trace a specific transaction across nodes.
Join a transaction's work to its ledger with `{span.current_ledger_seq=<N>}`.
**Prometheus labels**: `local`, `suppressed`, `tx_type`, `ter_result`, `stage` (SpanMetrics dimensions).
#### Transaction Queue (TxQ) Attributes
| Attribute | Type | Set On | Description |
| -------------------- | ------- | ------------------------------ | ----------------------------------------------------------- |
| `tx_hash` | string | `txq.enqueue`, `txq.accept_tx` | Transaction hash |
| `tx_type` | string | `txq.enqueue` | Transaction type name |
| `txq_status` | string | `txq.enqueue`, `txq.accept_tx` | Queue outcome (e.g. `queued`, `applied_direct`, `rejected`) |
| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the queued tx |
| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level for inclusion |
| `num_cleared` | int64 | `txq.batch_clear` | Entries cleared in a batch |
| `queue_size` | int64 | `txq.accept` | Current TxQ depth |
| `ledger_changed` | boolean | `txq.accept` | Whether the ledger changed since last attempt |
| `ter_code` | int64 | `txq.accept_tx` | Transaction engine result code |
| `retries_remaining` | int64 | `txq.accept_tx` | Retries left before discard |
| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence number |
| `expired_count` | int64 | `txq.cleanup` | Number of expired entries cleared |
| Attribute | Type | Set On | Description |
| --------------------- | ------- | ------------------------------ | -------------------------------------------------------------------------------- |
| `tx_hash` | string | `txq.enqueue`, `txq.accept_tx` | Transaction hash |
| `tx_type` | string | `txq.enqueue` | Transaction type name |
| `current_ledger_seq` | int64 | `txq.enqueue` | Seq of the ledger being worked on — correlates the enqueue to the ledger trace |
| `current_ledger_hash` | string | `txq.enqueue` | Parent hash of that ledger (= `consensus.round` trace-id seed on the build path) |
| `txq_status` | string | `txq.enqueue`, `txq.accept_tx` | Queue outcome (e.g. `queued`, `applied_direct`, `rejected`) |
| `fee_level_paid` | int64 | `txq.enqueue` | Fee level paid by the queued tx |
| `required_fee_level` | int64 | `txq.enqueue` | Minimum fee level for inclusion |
| `num_cleared` | int64 | `txq.batch_clear` | Entries cleared in a batch |
| `queue_size` | int64 | `txq.accept` | Current TxQ depth |
| `ledger_changed` | boolean | `txq.accept` | Whether the ledger changed since last attempt |
| `ter_code` | int64 | `txq.accept_tx` | Transaction engine result code |
| `retries_remaining` | int64 | `txq.accept_tx` | Retries left before discard |
| `ledger_seq` | int64 | `txq.cleanup` | Ledger sequence number |
| `expired_count` | int64 | `txq.cleanup` | Number of expired entries cleared |
**Prometheus label**: `txq_status` (SpanMetrics dimension).

View File

@@ -80,14 +80,14 @@ All spans instrumented in xrpld, grouped by subsystem:
### Transaction Spans
| Span Name | Source File | Attributes | Description |
| --------------- | --------------- | --------------------------------------------------------------------------------- | ------------------------------------- |
| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied` | Transaction submission and processing |
| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status` | Transaction received from peer relay |
| `tx.apply` | BuildLedger.cpp | `ledger_seq`, `tx_count`, `tx_failed` | Transaction set applied per ledger |
| `tx.preflight` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Stateless checks stage |
| `tx.preclaim` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Ledger-aware checks stage |
| `tx.transactor` | Transactor.cpp | `stage`, `tx_type`, `ter_result`, `applied` | Apply stage (transactor runs) |
| Span Name | Source File | Attributes | Description |
| --------------- | --------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `tx.process` | NetworkOPs.cpp | `tx_hash`, `local`, `path`, `tx_type`, `fee`, `sequence`, `ter_result`, `applied`, `current_ledger_seq` | Transaction submission and processing |
| `tx.receive` | PeerImp.cpp | `peer_id`, `tx_hash`, `tx_type`, `peer_version`, `suppressed`, `tx_status`, `current_ledger_seq` | Transaction received from peer relay |
| `tx.apply` | BuildLedger.cpp | `ledger_seq`, `tx_count`, `tx_failed` | Transaction set applied per ledger |
| `tx.preflight` | applySteps.cpp | `stage`, `tx_type`, `ter_result` | Stateless checks stage |
| `tx.preclaim` | applySteps.cpp | `stage`, `tx_type`, `ter_result`, `current_ledger_seq`, `current_ledger_hash` | Ledger-aware checks stage |
| `tx.transactor` | Transactor.cpp | `stage`, `tx_type`, `ter_result`, `applied`, `current_ledger_seq`, `current_ledger_hash` | Apply stage (transactor runs) |
The three apply-pipeline spans (`tx.preflight`, `tx.preclaim`, `tx.transactor`)
share a deterministic `trace_id` from `txID[0:16]`, so they group under one
@@ -95,16 +95,23 @@ trace per transaction. The `stage` attribute (`preflight` / `preclaim` /
`apply`) drives the collector spanmetrics `stage` dimension, giving per-stage
RED metrics on the _Transaction Overview_ dashboard.
`current_ledger_seq` is the current (open/in-flight) ledger index a span acted on
— the ledger being worked on, not an established one — so a transaction's
txID-keyed spans can be joined to the ledger trace it targeted
(`span.current_ledger_seq`). The view-bearing stages (`tx.preclaim`,
`tx.transactor`) also carry `current_ledger_hash` (the current ledger's parent
hash); `tx.preflight` is stateless and omits both.
### Transaction Queue Spans
| Span Name | Source File | Attributes | Description |
| ------------------ | ----------- | -------------------------------------------------------- | -------------------------------------------------- |
| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type` | Transaction enqueue decision (child of tx.process) |
| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) |
| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account |
| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions |
| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept |
| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries |
| Span Name | Source File | Attributes | Description |
| ------------------ | ----------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `txq.enqueue` | TxQ.cpp | `tx_hash`, `tx_type`, `current_ledger_seq`, `current_ledger_hash` | Enqueue decision; parents to `tx.process` on the submission path (explicit context), a root on the open-ledger rebuild path — `current_ledger_seq` correlates it to the ledger in both cases |
| `txq.apply_direct` | TxQ.cpp | -- | Direct apply attempt (bypassing queue) |
| `txq.batch_clear` | TxQ.cpp | -- | Batch clear of queued transactions for an account |
| `txq.accept` | TxQ.cpp | `queue_size`, `ledger_changed` | Ledger-close accept loop over queued transactions |
| `txq.accept_tx` | TxQ.cpp | `tx_hash`, `retries_remaining`, `ter_code`, `txq_status` | Per-transaction apply during accept |
| `txq.cleanup` | TxQ.cpp | `ledger_seq` | Post-close cleanup of expired queue entries |
### PathFinding Spans
@@ -347,6 +354,28 @@ sum by (stage) (rate(span_calls_total{span_name=~"tx.preflight|tx.preclaim|tx.tr
| "Was consensus outcome normal?" | `consensus.accept` | `consensus_state` |
| "Did a validator bow out?" | `consensus.proposal.send` | `is_bow_out` |
| "Which ledger was validated?" | `consensus.validation.send` | `ledger_hash` |
| "What tx work fed a given ledger?" | `tx.*` / `txq.*` | `current_ledger_seq` |
### Correlating a transaction to the ledger it was worked on
The `tx.process`, `tx.receive`, `txq.enqueue`, `tx.preclaim`, and `tx.transactor`
spans carry `current_ledger_seq` — the open/in-flight ledger they acted on (not
an established ledger). Because these spans are keyed on the transaction id (their
own trace) while the ledger/consensus spans are keyed on the ledger, use the
attribute to bridge the two id-spaces:
```
# All transaction-side work recorded against ledger N
{span.current_ledger_seq = N}
# Join to the ledger build/consensus trace for the same ledger
{name="ledger.build" && span.ledger_seq = N}
```
`txq.enqueue` and the view-bearing apply stages also carry `current_ledger_hash`
(the current ledger's parent hash), which equals the `consensus.round`
deterministic trace-id seed on the consensus-build path. `tx.preflight` is
stateless and omits both attributes.
---

View File

@@ -146,6 +146,19 @@ inline constexpr auto closeResolutionMs = makeStr("close_resolution_ms");
*/
inline constexpr auto ledgerHash = makeStr("ledger_hash");
inline constexpr auto fullValidation = makeStr("full_validation");
/**
* Shared "ledger being worked on" attrs — the open/tentative or in-flight
* consensus-build ledger a transaction is applied into, NOT an established or
* validated ledger (that is `ledgerSeq`, set on ledger.build / consensus.round).
* Named after the RPC field `ledger_current_index` and the `currentLedgerSeq`
* log usage. Reused by the tx lifecycle, apply-pipeline, and TxQ spans so a
* transaction's work can be correlated to the ledger it targeted.
* `currentLedgerHash` is the current view's parent-ledger hash, which equals the
* consensus.round deterministic trace-id seed on the consensus-build path.
*/
inline constexpr auto currentLedgerSeq = makeStr("current_ledger_seq");
inline constexpr auto currentLedgerHash = makeStr("current_ledger_hash");
} // namespace attr
// ===== Shared attribute values =============================================

View File

@@ -101,6 +101,15 @@ inline constexpr auto transactor = join(seg::tx, op::transactor);
// ===== Attribute keys ======================================================
namespace attr {
/**
* Shared "ledger being worked on" attrs (defined in SpanNames.h). Set on
* tx.preclaim and tx.transactor (both run against a view whose seq() is the
* ledger being applied into). tx.preflight is stateless (no view) and is the
* documented exception — it carries neither.
*/
using ::xrpl::telemetry::attr::currentLedgerHash;
using ::xrpl::telemetry::attr::currentLedgerSeq;
/**
* "stage" — which apply-pipeline stage this span represents. Drives the
* collector spanmetrics `stage` dimension for per-stage RED metrics.

View File

@@ -1607,6 +1607,13 @@ Transactor::operator()()
span.setAttribute(telemetry::tx_apply_span::attr::stage, telemetry::tx_apply_span::val::apply);
if (auto const* fmt = TxFormats::getInstance().findByType(ctx_.tx.getTxnType()))
span.setAttribute(telemetry::tx_apply_span::attr::txType, fmt->getName().c_str());
// The ledger being worked on (seq + parent hash) — correlates this apply
// stage to the ledger/consensus trace it is building into.
span.setAttribute(
telemetry::tx_apply_span::attr::currentLedgerSeq, static_cast<std::int64_t>(view().seq()));
span.setAttribute(
telemetry::tx_apply_span::attr::currentLedgerHash,
to_string(view().header().parentHash).c_str());
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();

View File

@@ -78,9 +78,20 @@ txTypeName(TxType txnType)
* @param name Full span name (tx_apply_span::preflight / ::preclaim).
* @param stage Stage attribute value (tx_apply_span::val::*).
* @param tx The transaction supplying the id and type.
* @param curLedgerSeq Seq of the ledger being worked on, set as the
* current_ledger_seq attribute. Passed by the
* view-bearing stages (preclaim); nullopt for the
* stateless preflight, which then omits it.
* @param curLedgerParentHash Parent hash of that ledger, set as
* current_ledger_hash. nullptr to omit.
*/
[[nodiscard]] telemetry::SpanGuard
makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx)
makeStageSpan(
std::string_view name,
std::string_view stage,
STTx const& tx,
std::optional<LedgerIndex> curLedgerSeq = std::nullopt,
uint256 const* curLedgerParentHash = nullptr)
{
auto const txID = tx.getTransactionID();
auto span = telemetry::SpanGuard::hashSpan(
@@ -92,6 +103,17 @@ makeStageSpan(std::string_view name, std::string_view stage, STTx const& tx)
span.setAttribute(telemetry::tx_apply_span::attr::stage, stage);
if (char const* typeName = txTypeName(tx.getTxnType()))
span.setAttribute(telemetry::tx_apply_span::attr::txType, typeName);
// The ledger being worked on — set only by the view-bearing stages
// (preclaim/transactor). Preflight is stateless and passes nullopt, so
// it carries no ledger attribute (documented exception).
if (curLedgerSeq)
span.setAttribute(
telemetry::tx_apply_span::attr::currentLedgerSeq,
static_cast<std::int64_t>(*curLedgerSeq));
if (curLedgerParentHash)
span.setAttribute(
telemetry::tx_apply_span::attr::currentLedgerHash,
to_string(*curLedgerParentHash).c_str());
}
return span;
}
@@ -224,8 +246,14 @@ static TER
invokePreclaim(PreclaimContext const& ctx)
{
// Trace the preclaim stage under the transaction's deterministic trace_id.
// Preclaim has a ledger view, so tag the span with the ledger being worked
// on (seq + parent hash) to correlate it to the ledger/consensus trace.
auto span = makeStageSpan(
telemetry::tx_apply_span::preclaim, telemetry::tx_apply_span::val::preclaim, ctx.tx);
telemetry::tx_apply_span::preclaim,
telemetry::tx_apply_span::val::preclaim,
ctx.tx,
ctx.view.seq(),
&ctx.view.header().parentHash);
try
{
// use name hiding to accomplish compile-time polymorphism of static

View File

@@ -51,3 +51,12 @@ TEST(TxApplySpanNames, stage_values_are_the_three_pipeline_stages)
EXPECT_EQ(std::string_view(tx_apply_span::val::preclaim), "preclaim");
EXPECT_EQ(std::string_view(tx_apply_span::val::apply), "apply");
}
TEST(TxApplySpanNames, current_ledger_keys_are_shared_constants)
{
// "ledger being worked on" keys, re-exported from the base SpanNames.h so
// every emitter (tx.*, txq.*, apply pipeline) shares one string. Set on
// preclaim/transactor (view-bearing); preflight omits them (stateless).
EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerSeq), "current_ledger_seq");
EXPECT_EQ(std::string_view(tx_apply_span::attr::currentLedgerHash), "current_ledger_hash");
}

View File

@@ -1390,6 +1390,12 @@ NetworkOPsImp::processTransaction(
auto span = std::make_shared<SpanGuard>(txProcessSpan(transaction->getID()));
span->setAttribute(tx_span::attr::txHash, to_string(transaction->getID()).c_str());
span->setAttribute(tx_span::attr::local, bLocal);
// The current (open) ledger index at submission/relay time — the ledger
// being worked on. Correlates this tx.process to the ledger trace; the tx
// has not yet been applied to a specific ledger here, so there is no hash.
span->setAttribute(
tx_span::attr::currentLedgerSeq,
static_cast<std::int64_t>(ledgerMaster_.getCurrentLedgerIndex()));
if (auto const& stx = transaction->getSTransaction())
{
if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType()))
@@ -1600,8 +1606,19 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
if (e.failType == FailHard::Yes)
flags |= TapFailHard;
// Parent the txq.enqueue span to this tx's tx.process span
// via an explicit captured context (the parent is explicit,
// not ambient-inherited). Null on the open-ledger rebuild
// path, where no tx.process span exists.
auto const txProcessCtx =
(e.span && *e.span) ? e.span->spanContext() : telemetry::SpanContext{};
auto const result = registry_.get().getTxQ().apply(
registry_.get().getApp(), view, e.transaction->getSTransaction(), flags, j);
registry_.get().getApp(),
view,
e.transaction->getSTransaction(),
flags,
j,
txProcessCtx.isValid() ? &txProcessCtx : nullptr);
e.result = result.ter;
e.applied = result.applied;
changed = changed || result.applied;

View File

@@ -16,6 +16,7 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/telemetry/SpanGuard.h>
#include <xrpl/tx/applySteps.h>
#include <boost/circular_buffer.hpp>
@@ -334,7 +335,8 @@ public:
OpenView& view,
std::shared_ptr<STTx const> const& tx,
ApplyFlags flags,
beast::Journal j);
beast::Journal j,
telemetry::SpanContext const* parentCtx = nullptr);
/**
* Fill the new open ledger with transactions from the queue.

View File

@@ -749,13 +749,35 @@ TxQ::apply(
OpenView& view,
std::shared_ptr<STTx const> const& tx,
ApplyFlags flags,
beast::Journal j)
beast::Journal j,
telemetry::SpanContext const* parentCtx)
{
using namespace telemetry;
ScopedSpanGuard span(TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue);
// Parent the enqueue span to the caller's tx.process span when a context is
// supplied (submission path), using the explicit-context factory: the parent
// comes from the captured ctx rather than being inherited from whatever span
// is ambient, and this ScopedSpanGuard's scope is RAII-bounded to this fully
// synchronous call (no coroutine yield), so no unrelated parent leaks in and
// its scope cannot leak out onto a reused worker.
// On the open-ledger rebuild path parentCtx is null and this is a root; the
// current_ledger_seq attribute below correlates it to the ledger instead.
// A lambda (not a ternary) picks the factory: ScopedSpanGuard's move ctor is
// deleted, so guaranteed copy elision on each return is the only way to
// construct it conditionally.
auto span = [&]() -> ScopedSpanGuard {
if (parentCtx && parentCtx->isValid())
return ScopedSpanGuard::childSpan(txq_span::enqueue, *parentCtx);
return ScopedSpanGuard(
TraceCategory::Transactions, txq_span::prefix::txq, txq_span::op::enqueue);
}();
span.setAttribute(txq_span::attr::txHash, to_string(tx->getTransactionID()).c_str());
if (auto const* fmt = TxFormats::getInstance().findByType(tx->getTxnType()))
span.setAttribute(txq_span::attr::txType, fmt->getName().c_str());
// The ledger being worked on (open/tentative apply or in-flight consensus
// build) — correlates this enqueue to the ledger trace in every context.
span.setAttribute(txq_span::attr::currentLedgerSeq, static_cast<std::int64_t>(view.seq()));
span.setAttribute(
txq_span::attr::currentLedgerHash, to_string(view.header().parentHash).c_str());
// Default outcome; overridden below on the direct-apply and queued paths.
// Every other early return leaves the tx rejected from the queue.
span.setAttribute(txq_span::attr::txqStatus, txq_span::val::rejected);

View File

@@ -73,12 +73,22 @@ inline constexpr auto acceptTx = makeStr("accept_tx");
inline constexpr auto cleanup = makeStr("cleanup");
} // namespace op
// ===== Full span names (prefix.op) ===========================================
//
// Joined "txq.<op>" names for the explicit-context factories
// (SpanGuard::childSpan(name, ctx)) that take one full span name rather than a
// prefix/suffix pair.
inline constexpr auto enqueue = join(prefix::txq, op::enqueue);
// ===== Attribute keys ======================================================
namespace attr {
/**
* Canonical shared constants (defined in SpanNames.h).
*/
using ::xrpl::telemetry::attr::currentLedgerHash;
using ::xrpl::telemetry::attr::currentLedgerSeq;
using ::xrpl::telemetry::attr::ledgerSeq;
using ::xrpl::telemetry::attr::txHash;

View File

@@ -1320,6 +1320,12 @@ PeerImp::handleTransaction(
auto span = std::make_shared<SpanGuard>(txReceiveSpan(txID, *m));
span->setAttribute(tx_span::attr::txHash, to_string(txID).c_str());
span->setAttribute(tx_span::attr::peerId, static_cast<int64_t>(id_));
// The current (open) ledger index when the relayed tx was received —
// the ledger being worked on. Correlates this tx.receive to the ledger
// trace; not yet applied to a specific ledger here, so no hash.
span->setAttribute(
tx_span::attr::currentLedgerSeq,
static_cast<std::int64_t>(app_.getLedgerMaster().getCurrentLedgerIndex()));
if (auto const* fmt = TxFormats::getInstance().findByType(stx->getTxnType()))
span->setAttribute(tx_span::attr::txType, fmt->getName().c_str());
if (auto const version = getVersion(); !version.empty())

View File

@@ -50,6 +50,13 @@ namespace attr {
using ::xrpl::telemetry::attr::peerId;
using ::xrpl::telemetry::attr::txHash;
/**
* "current_ledger_seq" — the open/current ledger index at the time the tx is
* received or submitted (the ledger being worked on), NOT an established ledger.
* No parent-ledger hash here: these entry points have no ledger view.
*/
using ::xrpl::telemetry::attr::currentLedgerSeq;
/**
* "local" — whether tx originated locally.
*/