Skip to content

feat(rt/tracing): introduce tracing executors - #323

Open
cratelyn wants to merge 8 commits into
sean/yopytotwomtufrom
kate/yopytotwomtu.2
Open

cratelyn wants to merge 8 commits into
sean/yopytotwomtufrom
kate/yopytotwomtu.2

Conversation

@cratelyn

@cratelyn cratelyn commented Sep 18, 2026

Copy link
Copy Markdown
Member

📜 background

see #322 for more information.

in #166, hyper_util::rt::TokioExecutor<E> began propagating the currently active tracing::Span to spawned tasks when hyper::rt::Executor::execute() is called.

hyperium/hyper#3904 reports how this has since caused some issues, in part because some observability systems do not export spans until they are closed. in #322, we removed this behavior from hyper_util::rt::TokioExecutor<E>, with a temporary feature flag that can be used to preserve the previous behavior.

📊 rt::tracing

this branch introduces a new submodule, hyper_util::rt::tracing. this submodule contains a collection of Execute wrappers to allow tracing spans to be propagated according to a few different common strategies.

  • WithSpanExecutor<E> instruments background tasks with a provided span.
  • CurrentSpanExecutor<E> instruments background tasks with the current span when execute() is called.
  • MkSpanExecutor<E, F> instruments background tasks with a span created by a provided callback.

this submodule is gated by the #[cfg(tracing)] feature flag.

🌱 Example

this branch adds an example similar to client.rs that makes use of the tracing functionality in rt::tracing. this example uses the current span executor, and runs the client in an info-level span to demonstrate the functionality.

the fmt feature is added to our development dependency upon tracing_subscriber.

when run against the server example, the logs look like the following:

; cargo run --example client_tracing --features 'client client-legacy http1 tokio tracing' -- http://127.0.0.1:8000

2026-09-21T18:12:10.840038Z  INFO client_tracing: tracing subscriber initialized
2026-09-21T18:12:10.840072Z  INFO client_tracing: parsed URL url=http://127.0.0.1:8000/
2026-09-21T18:12:10.840137Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: checkout waiting for idle connection: ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840181Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: Http::connect; scheme=Some("http"), host=Some("127.0.0.1"), port=Some(Port(8000))
2026-09-21T18:12:10.840199Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: connecting to 127.0.0.1:8000
2026-09-21T18:12:10.840335Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: connected to 127.0.0.1:8000
2026-09-21T18:12:10.840369Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: http1 handshake complete, spawning background dispatcher task
2026-09-21T18:12:10.840386Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: waiting for connection to be ready
2026-09-21T18:12:10.840422Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: connection is ready
2026-09-21T18:12:10.840432Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: checkout dropped for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840666Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: put; add idle connection for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840689Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: pooling idle connection for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840709Z  INFO client_tracing: received response resp.version=HTTP/1.1 resp.status=200 OK resp.body=Hello, world!

this demonstrates the informative context (the destination url in this case) that is attached to background tasks spawned by the client.

@cratelyn
cratelyn changed the base branch from master to sean/yopytotwomtu September 18, 2026 19:54
@cratelyn cratelyn changed the title kate/yopytotwomtu.2 feat(rt/tracing): introduce WithSpanExecutor Sep 18, 2026
@cratelyn

This comment was marked as outdated.

Comment thread src/rt/tracing.rs
Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn

This comment was marked as resolved.

@cratelyn cratelyn changed the title feat(rt/tracing): introduce WithSpanExecutor feat(rt/tracing): introduce tracing executors Sep 20, 2026
Comment thread src/rt/tokio.rs
@cratelyn
cratelyn force-pushed the kate/yopytotwomtu.2 branch 3 times, most recently from 7277f06 to 7899d89 Compare September 21, 2026 17:18
see #322 for more
information.

this commit introduces an additional `Executor` implementation to
accompany the `CurrentSpanExecutor` that executes spawned futures within
the current span at time of execution.

this would provide an alternative for users that wish to provide tracing
information, but do not want to run background futures in the current
span, which can interfere with some observability systems.

Signed-off-by: katelyn martin <git@katelyn.world>
this propagates the construction span.

Signed-off-by: katelyn martin <git@katelyn.world>
Signed-off-by: katelyn martin <git@katelyn.world>
Signed-off-by: katelyn martin <git@katelyn.world>
now that we have added documentation to this submodule with information
about selecting an executor, along with examples, we should make this
submodule public so that users can see it.

Signed-off-by: katelyn martin <git@katelyn.world>
Signed-off-by: katelyn martin <git@katelyn.world>
this is an example similar to `client.rs` that makes use of the tracing
functionality in `rt::tracing`. this example uses the current span
executor, and runs the client in an info-level span to demonstrate the
functionality.

the `fmt` feature is added to our development dependency upon
`tracing_subscriber`.

when run against the server example, the logs look like the following:

```
; cargo run --example client_tracing --features 'client client-legacy http1 tokio tracing' -- http://127.0.0.1:8000

2026-09-21T18:12:10.840038Z  INFO client_tracing: tracing subscriber initialized
2026-09-21T18:12:10.840072Z  INFO client_tracing: parsed URL url=http://127.0.0.1:8000/
2026-09-21T18:12:10.840137Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: checkout waiting for idle connection: ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840181Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: Http::connect; scheme=Some("http"), host=Some("127.0.0.1"), port=Some(Port(8000))
2026-09-21T18:12:10.840199Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: connecting to 127.0.0.1:8000
2026-09-21T18:12:10.840335Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::connect::http: connected to 127.0.0.1:8000
2026-09-21T18:12:10.840369Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: http1 handshake complete, spawning background dispatcher task
2026-09-21T18:12:10.840386Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: waiting for connection to be ready
2026-09-21T18:12:10.840422Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::client: connection is ready
2026-09-21T18:12:10.840432Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: checkout dropped for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840666Z TRACE sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: put; add idle connection for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840689Z DEBUG sending request{url=http://127.0.0.1:8000/}: hyper_util::client::legacy::pool: pooling idle connection for ("http", 127.0.0.1:8000)
2026-09-21T18:12:10.840709Z  INFO client_tracing: received response resp.version=HTTP/1.1 resp.status=200 OK resp.body=Hello, world!
```

this demonstrates the informative context (the destination url in this
case) that is attached to background tasks spawned by the client.

Signed-off-by: katelyn martin <git@katelyn.world>
@cratelyn
cratelyn marked this pull request as ready for review September 21, 2026 18:20

@cratelyn cratelyn left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 notes for reviewers

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the pr description for logs from this example.

Comment thread src/rt/tokio.rs
Comment on lines +79 to +80
/// See the module-level documentation of [`rt::tracing`](crate::rt::tracing)
/// for more information about propagating [`tracing`] spans to spawned tasks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've added a link to the module-level documentation where there is more information about choosing a particular executor. we still mention CurrentSpanExecutor to provide a likely workable default for most users.

Comment thread src/rt/tracing.rs
}

#[test]
fn mk_span_executor_current_propagates_causal_span_relationships() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a test to show that we can spawn background tasks that "follow from" the current span. there is a bit of boilerplate to record and check this, since it isn't visible through Span's public interface: https://docs.rs/tracing/latest/tracing/struct.Span.html

Comment thread src/rt/tracing.rs
Comment on lines +1 to +23
//! Runtime components for use with [`tracing`].
//!
//! This module provides [`Executor`] implementations that configure
//! instrumentation of spawned futures. These [`Executor`]s can propagate
//! tracing [`Span`]s to futures spawned onto the async runtime. See the
//! crate-level documentation of [`tracing`] for [more information] about spans.
//!
//! # Choosing an [`Executor`].
//!
//! Hyper spawns [`Future`]s onto an [`Executor`], to avoid tightly coupling
//! APIs to any particular async runtime. This includes background tasks that
//! might help service I/O for the lifetime of a connection, for example.
//!
//! Some [`Subscriber`][tracing::subscriber] implementations have different
//! semantics regarding the lifecycle of [`Span`]s. Integrations with
//! OpenTelemetry collectors, for example, might not emit the events within
//! the context of a span until it is closed. Conversely, subscribers that
//! print traces to the terminal may not have to contend with these details when
//! instrumenting long-lived tasks that run in the background.
//!
//! This module provides different executors to help pass tracing context in
//! the manner appropriate for your application. For most typical applications,
//! [`CurrentSpanExecutor<E>`] should suffice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because tracing spans can be a little bit tricky, i tried documenting this in a legible and descriptive way, and including examples (below) for some common span propagation strategies. open to suggestions about how to make this more helpful!

This branch has not been deployed

No deployments
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