Skip to content

Open a tracing span per transaction and parent in-tx queries under it#1

Open
Diggsey wants to merge 1 commit into
db-tracing-spansfrom
db-tracing-spans-transactions
Open

Open a tracing span per transaction and parent in-tx queries under it#1
Diggsey wants to merge 1 commit into
db-tracing-spansfrom
db-tracing-spans-transactions

Conversation

@Diggsey
Copy link
Copy Markdown

@Diggsey Diggsey commented May 21, 2026

Builds on the per-query span work: Transaction opens its own hardcoded INFO-level db.transaction span at begin, recorded on the connection. The backend executor's QueryLogger::new_under_span parents the query span under the connection's currently open tx span, so queries run via &mut *tx show up as children of the transaction in the trace tree without the caller having to wrap anything.

Span shape (OTel semconv): db.system.name, db.operation.name = "BEGIN", db.transaction.outcome (Empty, recorded as "committed" / "rolled_back" / "dropped" before close), otel.kind = "client".

Only the outermost transaction owns the span; nested savepoints share it (the SAVEPOINT / RELEASE / ROLLBACK TO SQL query spans inside are the savepoint markers). Same !Send discipline as the query span — the connection stores only Span plus the caller's Span::current() id at begin time, no EnteredSpan across awaits.

The caller's Span::current() id at begin time is also the heuristic for auto-parenting: at query time the executor's query_parent_span returns the tx span only if the current span hasn't changed since begin. If the caller has wrapped a region in their own .instrument(my_span) between begin and the query, we return None and the query parents under the caller's current — respecting their explicit hierarchy instead of clobbering it.

Transaction::span() is publicly exposed for callers passing the transaction around procedurally who want to .instrument(tx.span()) their own futures to nest user spans under the transaction.

Trait additions (all default no-op / None so out-of-tree drivers keep compiling): TransactionManager::set_transaction_span, clear_transaction_span, current_transaction_span, query_parent_span. SQLite threads parent_span through ConnectionWorker::execute so the worker thread enters the right parent before opening the query span.

Does your PR solve an issue?

fixes transact-rs#1896

Is this a breaking change?

No

This PR was written with assistance from Claude Code

Builds on the per-query span work: `Transaction` opens its own
hardcoded INFO-level `db.transaction` span at begin, recorded on the
connection. The backend executor's `QueryLogger::new_under_span`
parents the query span under the connection's currently open tx span,
so queries run via `&mut *tx` show up as children of the transaction
in the trace tree without the caller having to wrap anything.

Span shape (OTel semconv): `db.system.name`, `db.operation.name =
"BEGIN"`, `db.transaction.outcome` (Empty, recorded as "committed" /
"rolled_back" / "dropped" before close), `otel.kind = "client"`.

Only the outermost transaction owns the span; nested savepoints share
it (the SAVEPOINT / RELEASE / ROLLBACK TO SQL query spans inside are
the savepoint markers). Same `!Send` discipline as the query span —
the connection stores only `Span` plus the caller's `Span::current()`
id at begin time, no `EnteredSpan` across awaits.

The caller's `Span::current()` id at begin time is also the heuristic
for auto-parenting: at query time the executor's `query_parent_span`
returns the tx span only if the current span hasn't changed since
begin. If the caller has wrapped a region in their own
`.instrument(my_span)` between begin and the query, we return `None`
and the query parents under the caller's current — respecting their
explicit hierarchy instead of clobbering it.

`Transaction::span()` is publicly exposed for callers passing the
transaction around procedurally who want to `.instrument(tx.span())`
their own futures to nest user spans under the transaction.

Trait additions (all default no-op / `None` so out-of-tree drivers
keep compiling): `TransactionManager::set_transaction_span`,
`clear_transaction_span`, `current_transaction_span`,
`query_parent_span`. SQLite threads `parent_span` through
`ConnectionWorker::execute` so the worker thread enters the right
parent before opening the query span.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant