Skip to content

refactor!: move the opentelemetry integration into irpc-opentelemetry - #129

Open
Frando wants to merge 8 commits into
mainfrom
Frando/opentelemetry-crate
Open

Frando wants to merge 8 commits into
mainfrom
Frando/opentelemetry-crate

Conversation

@Frando

@Frando Frando commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Fixes #111

irpc currently depends on opentelemetry and tracing-opentelemetry behind the tracing-opentelemetry feature and exposes their types in the public API. Both crates are at 0.* versions with rather frequent releases, so we shouldn't expose them in our 1.0 API.

This PR removes both dependencies from irpc. Instead, irpc offers a generic way to inject span context through a small Propagator trait, with one method to write span context into request headers (on the client) and one method to set the parent of a span from the request headers (on the server). The propagator impl to use is found through the tracing subscriber: add the layer from irpc::span_propagation::layer(propagator) to it. The layer has a LevelFilter::OFF per-layer filter, because an unfiltered layer would raise the max level to TRACE when other layers use per-layer filters. So the subscriber must implement LookupSpan, as tracing-opentelemetry also requires. irpc finds the layer with Dispatch::downcast_ref, the same way tracing-opentelemetry finds its own layer, so there is no process-wide global, and tests can use different propagators with tracing::subscriber::set_default (which is thread-local, so the server task must run on the same thread, as in the new test). The propagator and the layer are behind the new feature span-propagation (not default), so tracing-subscriber (0.x) is not in the public API of a default build. The wire format does not depend on the feature: without it, a client sends no span context.

This PR then adds a new in-repo crate irpc-opentelemetry, which we would not release as 1.0. It implements the trait with the global text map propagator of opentelemetry, and can follow new opentelemetry versions with breaking releases. It has its own version (shared-version = false for cargo-release), and the OpenTelemetry tests and example move into it.

Other tracing backends can implement the trait too, see the new test in irpc that checks the hook with a fake propagator.

Breaking changes

  • removed: the feature tracing-opentelemetry. Add irpc-opentelemetry and add irpc_opentelemetry::layer() to the tracing subscriber. irpc-opentelemetry enables the span-propagation feature of irpc.
  • removed: SpanContextCarrier::from_current and SpanContextCarrier::to_context, and the Injector and Extractor impls of SpanContextCarrier.

Additions

  • the feature span-propagation (not default), with span_propagation::Propagator and span_propagation::layer.
  • SpanContextCarrier::get, SpanContextCarrier::set, and SpanContextCarrier::keys.

Frando added 3 commits October 2, 2026 13:39
irpc currently depends on `opentelemetry` and `tracing-opentelemetry`
behind the `tracing-opentelemetry` feature, and exposes their types in
its public API. Their versions change often, so irpc 1.0 could not
update them without a major release (#111).

This PR removes both dependencies from irpc. irpc keeps the wire format
and gets a small `Propagator` trait: one method writes the context of a
span into the text headers of a request, one sets the parent of a span
from them. An application installs one propagator per process with
`irpc::span_propagation::set_propagator`. The propagator does not change
the wire format: a protocol with `span_propagation` always sends the
`Option<SpanContextCarrier>`, and without a propagator its value is
`None`.

The new crate `irpc-opentelemetry` (0.x) implements the trait with the
global text map propagator of `opentelemetry`, and can follow new
`opentelemetry` versions with breaking releases. Other tracing backends
can implement the trait too. The span propagation tests and example
move to the new crate. A new test in irpc checks the hook with a fake
propagator. For cargo-release, the new crate has its own version and
does not share the version of irpc.

* removed: the feature `tracing-opentelemetry`. Add `irpc-opentelemetry` and call `irpc_opentelemetry::install()` once at startup.
* removed: `SpanContextCarrier::from_current` and `SpanContextCarrier::to_context`, and the `Injector` and `Extractor` impls of `SpanContextCarrier`.
* changed: `rpc` enables the `rt` feature of `tokio`, for the task-local of the span context.
The span propagator of irpc is currently a process-wide static, set once
with `set_propagator`. A process can only have one, a second call fails,
and tests that need different propagators must live in separate test
binaries. The setup is also apart from the rest of the tracing setup.

This PR removes the static. irpc gets a `PropagatorLayer`, a tracing
layer that holds a propagator and records nothing. irpc finds it with
`Dispatch::downcast_ref`, the same way `tracing-opentelemetry` finds its
own layer. The client looks in the subscriber of the current thread, and
the server in the subscriber of the request span. Without the layer,
requests send `None` as their span context, as before. The wire format
does not change.

`rpc` now enables `tracing-subscriber` without default features, for the
`Layer` trait.

`irpc-opentelemetry` replaces `install()` with `layer()`, which the
application adds to its subscriber next to the `tracing-opentelemetry`
layer.

## Breaking changes

* removed: `span_propagation::set_propagator` and `PropagatorAlreadySet`. Add a `PropagatorLayer` to the subscriber.
* removed: `irpc_opentelemetry::install`. Use `irpc_opentelemetry::layer()` in the subscriber.
With the previous commit, `rpc` enables `tracing-subscriber`, and
`PropagatorLayer` puts its `Layer` trait into the public API of irpc.
`tracing-subscriber` is 0.x, and most users of irpc do not propagate
span context.

This PR moves `Propagator`, `PropagatorLayer`, and the task-local of the
server behind the new feature `span-propagation`, which is not a default
feature. `irpc-opentelemetry` enables it.

The wire format stays under `rpc`: a protocol with `span_propagation`
always sends `Option<SpanContextCarrier>`. Without the feature, the
client sends `None` and the server ignores the carrier, the same as
without a layer. `set_span_parent_from_remote` stays without the
feature, because the code of the macro calls it, and does nothing.

`tokio/rt`, which only the task-local needs, moves from `rpc` to the
new feature.

## Breaking changes

* changed: `span_propagation::Propagator` and `span_propagation::PropagatorLayer` need the feature `span-propagation`.
@Frando
Frando force-pushed the Frando/opentelemetry-crate branch from dd0d8ed to 4d5d564 Compare October 2, 2026 11:39
@Frando
Frando requested a review from rklaehn October 5, 2026 07:46
@Frando

Frando commented Oct 5, 2026 •

Copy link
Copy Markdown
Member Author

I had a previous version that used a per-process static to store the propagator. However, I like the current version more: We can use the installed tracing subscriber and its layers to store the propagator. This makes it possible to have different propagators within one process as long as tracing-subscriber is configured accordingly, which nicely matches how tracing-opentelemetry already works.

The downside is that this adds tracing-subscriber to the API surface of irpc (when the span-propagation feature is enabled): PropagatorLayer implements tracing_subscriber::Layer. However, tracing-subscriber is fairly stable (0.3.0 was released in October 2021), and we have tracing in the public API anyway (for tracing::Span) so I think it's fine (a breaking tracing-subscriber 0.4 release will very likely come only for a breaking tracing 0.3 release).

@Frando
Frando marked this pull request as ready for review October 5, 2026 08:04
Frando added 3 commits October 5, 2026 10:09
…span context

Without context activation in tracing-opentelemetry, an empty carrier made
the request span a new root. Only set the parent if the extracted context
has a valid span.
The crate has no opentelemetry types in its API, so the re-exports are not
needed. The README and crate docs state the version bound on opentelemetry
and tracing-opentelemetry.
Comment thread irpc-opentelemetry/src/lib.rs Outdated

/// Returns a tracing layer that makes irpc propagate span context with [`OtelPropagator`].
pub fn layer() -> PropagatorLayer {
PropagatorLayer::new(OtelPropagator)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

My AI tells me that if you do it this way there will be a performance overhead for every tracing call when you use this layer. It suggests to set .with_filter(LevelFilter::OFF).

pub fn layer<S>() -> impl Layer<S>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    PropagatorLayer::new(OtelPropagator).with_filter(LevelFilter::OFF)
}

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.

Good finding. Fixed in the latest commit.

Comment thread src/span_propagation.rs

/// Run `fut` with `carrier`'s context installed as the per-task scope read by
/// [`set_span_parent_from_remote`].
#[cfg(feature = "span-propagation")]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is the only place where we have a dependency to tracing-subscriber 0.3 in the public API.

I was wondering if we should gate this with a feature flag that is named after the dependency version, e.g.

#[cfg(feature = "tracing-subscriber-03")]
impl<S: tracing::Subscriber> tracing_subscriber_03::Layer<S> for PropagatorLayer {}

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.

It is currently gated on span-propagation. I think we could add a tracing-subscriber-04 feature already now, that would then add the corresponding impl for tracing-subscriber 0.4. but yeah then we'd have to depend on both versions. so maybe adding tracing-subscriber-03 now makes sense. but span-propagation without this feature wouldn't do anything I think. Let me think this through a bit more

An unfiltered layer enables everything. With per-layer filters on the
other layers, adding it raised the max level to TRACE, so every `debug!`
and `trace!` reached the other layers.

`span_propagation::layer(propagator)` now returns the layer with a
`LevelFilter::OFF` per-layer filter. `PropagatorLayer` is private.

## Breaking changes

* removed: `span_propagation::PropagatorLayer`. Use `span_propagation::layer`.
* changed: `irpc_opentelemetry::layer` returns `impl Layer<S>`, and needs a subscriber that implements `LookupSpan`.
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.

Rework opentelemetry feature for 1.0 stability

2 participants