Skip to content

feat: Add OpenTelemetrySpanExt methods for direct span event creation #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 2, 2025

Conversation

alessandrobologna
Copy link
Contributor

This PR adds methods to OpenTelemetrySpanExt for directly adding OpenTelemetry span events, addressing the limitations of using tracing::event! for events with dynamic attributes.

Fixes #200

Motivation

As discussed in issue #200, the current tracing::event! macro requires attribute keys to be known at compile time. This makes it difficult or impossible to record OpenTelemetry span events with dynamic attributes (e.g., from processing arbitrary JSON payloads) without resorting to lossy serialization, which hinders observability.

This change provides a way to directly add opentelemetry::trace::Event objects with dynamic Vec<KeyValue> attributes, similar to how set_attribute allows setting dynamic span attributes. This improves flexibility, makes porting code easier, and aligns better with the capabilities of the OpenTelemetry Span API.

Solution

This PR extends the OpenTelemetrySpanExt trait with two new methods:

  1. add_otel_span_event<T>(&self, name: T, attributes: Vec<KeyValue>) where T: Into<Cow<'static, str>>: Adds an OpenTelemetry span event with the current timestamp.
  2. add_otel_span_event_with_timestamp<T>(&self, name: T, timestamp: SystemTime, attributes: Vec<KeyValue>) where T: Into<Cow<'static, str>>: Adds an OpenTelemetry span event with a specified timestamp.

The implementation uses the existing with_subscriber -> downcast_ref::<WithContext> -> with_context pattern found in other OpenTelemetrySpanExt methods (set_attribute, set_status) to access the internal OtelData builder and push the event.

The name add_otel_span_event was chosen over add_event to avoid potential confusion with the tracing::event! macro within the context of this crate.

This PR includes:

  • The trait method definitions and implementations in src/span_ext.rs.
  • Corresponding test cases in tests/span_ext.rs.
  • Updates to README.md documentation.
  • A new example file examples/opentelemetry-span-event.rs.
  • Updated CHANGELOG.md.

@alessandrobologna
Copy link
Contributor Author

Hi @djc (and other maintainers),
Just wanted to add a couple of notes about this PR:
Clippy Fix Commit: You'll notice the last commit (c54ba96) addresses a Clippy lint (double_ended_iterator_last in tests/metrics_publishing.rs). This warning was surfaced by the CI checks (-D warnings) and appeared unrelated to the core feature addition, so I kept it as a separate commit for clarity.
Versioning & Changelog: I haven't bumped the crate version in Cargo.toml, and the changelog entries for the new feature and example are currently under the ## Unreleased section in CHANGELOG.md.

I'm happy to handle the version bump and finalize the changelog entry if after your review this PR is ready to merge. I wasn't sure about the appropriate version target for this addition, since it's adding new API surface (non-breaking) to a 0.x crate, would you typically target a minor version bump (e.g., 0.31.0) or a patch release (e.g., 0.30.1)?
Happy to follow whatever the project's policy is.
Thanks for taking a look!

Copy link
Collaborator

@djc djc left a comment

Choose a reason for hiding this comment

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

This is mostly looking good, thanks!

Please keep the clippy commits separate and squash any additional changes into your original commit (might be easier to reorder them so that the clippy commits come first).

src/span_ext.rs Outdated
///
/// app_root.add_otel_span_event("job_completed", vec![KeyValue::new("status", "success")]);
/// ```
fn add_otel_span_event<T>(&self, name: T, attributes: Vec<KeyValue>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there other kinds of events in OTel? The name is a little verbose, so would be nice if we can get away with something a bit shorter like add_otel_event().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, there's a new WIP spec for OTel Events, as a new signal type. More background about it in here:

I think though that it's still far away from being widely implemented and adopted, so I am happy with the span events API for now (and I don't necessarily agree that it was a good idea to introduce a new signal type, but that's digressing).

I agree that the name is verbose, but add_otel_event would then be a little ambiguous when those specs are more widely adopted and known. Alternatively, we could use add_event (as in the OTel Span trait, in the same way that you have add_link) and since this is a method of a trait that is meant to "wrap" an OTel span, maybe it would be clear that is not a Tracing event. Let me know your preference and I will update the PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, let's do add_event() for now -- it seems there will still be sufficient opportunities to make breaking changes should the need arise.

src/span_ext.rs Outdated
///
/// app_root.add_otel_span_event("job_completed", vec![KeyValue::new("status", "success")]);
/// ```
fn add_otel_span_event<T>(&self, name: T, attributes: Vec<KeyValue>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: please use impl trait in arguments consistently where possible, so this should have name: impl Into<Cow<'static, str>> (everywhere).

@@ -0,0 +1,65 @@
use opentelemetry::trace::TracerProvider as _;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems a little niche so I'd prefer to avoid maintaining an example for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I can remove it

src/span_ext.rs Outdated
Comment on lines 322 to 328
if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
get_context.with_context(subscriber, id, move |data, _tracer| {
if let Some(event) = event.take() {
data.builder.events.get_or_insert_with(Vec::new).push(event);
}
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I'm aware this is mostly copy/pasting, but please use let-else to reduce rightward drift where possible. (If you want to fix up the original function too, that would be nice, but please keep it in a separate commit.)

CHANGELOG.md Outdated
Comment on lines 5 to 8
- Add `OpenTelemetrySpanExt::add_otel_span_event` function to allow adding OpenTelemetry
events directly to a `tracing::Span`, enabling the use of dynamic attribute keys.
- Add `OpenTelemetrySpanExt::add_otel_span_event_with_timestamp` function, similar to
`add_otel_span_event`, but allowing a specific `SystemTime` to be provided for the event.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I think these changes belong in a single bullet point.

CHANGELOG.md Outdated
events directly to a `tracing::Span`, enabling the use of dynamic attribute keys.
- Add `OpenTelemetrySpanExt::add_otel_span_event_with_timestamp` function, similar to
`add_otel_span_event`, but allowing a specific `SystemTime` to be provided for the event.
- Add `examples/opentelemetry-span-event.rs` demonstrating the usage of the new span event methods.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't forget to remove this when you remove the example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do

Use `.next_back()` instead of `.last()` on a `DoubleEndedIterator` in `tests/metrics_publishing.rs` to satisfy the `clippy::double_ended_iterator_last` lint, which was likely surfaced by CI flags (`-D warnings`) or a toolchain update.
@alessandrobologna alessandrobologna force-pushed the feature/add-otel-span-event branch from c54ba96 to f7cc03e Compare May 1, 2025 14:30
@alessandrobologna
Copy link
Contributor Author

@djc I think I have addressed all the changes requested. Let me know if you spot something else.

@djc
Copy link
Collaborator

djc commented May 2, 2025

As mentioned:

squash any additional changes into your original commit

Please squash the last two commits ("rename span event methods" and "use impl trait consistently"), which AFAICT only touch new code you have added, into your original commit ("add OpenTelemetrySpanExt methods").

Adds `add_event` and `add_event_with_timestamp` methods to the
`OpenTelemetrySpanExt` trait.

These methods allow users to add OpenTelemetry span events directly
to a `tracing::Span`, including events with dynamic attribute keys,
bypassing the limitations of the `tracing::event!` macro for such
use cases.

The signatures use `impl Into<Cow<'static, str>>` for the event name.

Includes:
- Trait method definitions and implementations.
- Corresponding test cases.
- Updates to README.md.
- Changelog entries.

Also incorporates refactoring based on review feedback:
- Use let-else patterns to reduce rightward drift.
- Rename methods for API simplification (`add_event`).
- Use `impl trait` consistently in method parameters.
- Remove example file (`examples/opentelemetry-span-event.rs`).
@alessandrobologna alessandrobologna force-pushed the feature/add-otel-span-event branch from 55880e7 to 78e8247 Compare May 2, 2025 12:35
@alessandrobologna
Copy link
Contributor Author

Hi @djc, thanks! I think I've addressed the feedback:

  • Kept the clippy fixes separate and moved them first.
  • Squashed the feature addition and subsequent refactorings into the final commit (78e8247).

The commit history should now match your suggestion. Let me know if there's something else!

@djc
Copy link
Collaborator

djc commented May 2, 2025

You actually didn't address my feedback exactly, because you also squashed the refactoring that changed other pre-existing code that you didn't add, so that's a little yucky from a reviewing perspective. However, I don't think it's worth doing another round trip on this, so I'll just merge this.

@djc djc merged commit 85e28ac into tokio-rs:v0.1.x May 2, 2025
14 checks passed
@alessandrobologna
Copy link
Contributor Author

alessandrobologna commented May 2, 2025

Apologies, I misunderstood, I thought you just wanted the clippy fixes separated. I see your point now.

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.

Feature Request: Support for Adding OpenTelemetry Span Events with Dynamic Attributes
2 participants