Skip to content

Commit

Permalink
core: add support for Arc<dyn Subscriber + ...>
Browse files Browse the repository at this point in the history
Users may wish to erase the type of a `Subscriber`
implementation, such as when it is dynamically constructed from a
complex parameterized type. PR tokio-rs#1358 added a `Subscriber` implementation
for `Box<dyn Subscriber + Send + Sync + 'static>`, allowing the use of
type-erased trait objects. In some cases, it may also be useful to share
a type-erased subscriber, _without_ using `Dispatch` --- such as when
different sets of `tracing-subscriber` subscribers are layered on one
shared subscriber.

This branch builds on tokio-rs#1358 by adding an `impl Subscriber for Arc<dyn
Subscriber + Send + Sync + 'static>`. I also added quick tests for both
`Arc`ed and `Box`ed subscribers.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
  • Loading branch information
hawkw authored and kaffarell committed May 22, 2024
1 parent 1c553a4 commit a4fba84
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 0 deletions.
78 changes: 78 additions & 0 deletions tracing-core/src/subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{span, Event, LevelFilter, Metadata};
use crate::stdlib::{
any::{Any, TypeId},
boxed::Box,
sync::Arc,
};

/// Trait representing the functions required to collect trace data.
Expand Down Expand Up @@ -639,3 +640,80 @@ impl Subscriber for Box<dyn Subscriber + Send + Sync + 'static> {
self.as_ref().downcast_raw(id)
}
}

impl Subscriber for Arc<dyn Subscriber + Send + Sync + 'static> {
#[inline]
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.as_ref().register_callsite(metadata)
}

#[inline]
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
self.as_ref().enabled(metadata)
}

#[inline]
fn max_level_hint(&self) -> Option<LevelFilter> {
self.as_ref().max_level_hint()
}

#[inline]
fn new_span(&self, span: &span::Attributes<'_>) -> span::Id {
self.as_ref().new_span(span)
}

#[inline]
fn record(&self, span: &span::Id, values: &span::Record<'_>) {
self.as_ref().record(span, values)
}

#[inline]
fn record_follows_from(&self, span: &span::Id, follows: &span::Id) {
self.as_ref().record_follows_from(span, follows)
}

#[inline]
fn event(&self, event: &Event<'_>) {
self.as_ref().event(event)
}

#[inline]
fn enter(&self, span: &span::Id) {
self.as_ref().enter(span)
}

#[inline]
fn exit(&self, span: &span::Id) {
self.as_ref().exit(span)
}

#[inline]
fn clone_span(&self, id: &span::Id) -> span::Id {
self.as_ref().clone_span(id)
}

#[inline]
fn try_close(&self, id: span::Id) -> bool {
self.as_ref().try_close(id)
}

#[inline]
#[allow(deprecated)]
fn drop_span(&self, id: span::Id) {
self.as_ref().try_close(id);
}

#[inline]
fn current_span(&self) -> span::Current {
self.as_ref().current_span()
}

#[inline]
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
if id == TypeId::of::<Self>() {
return Some(self as *const Self as *const _);
}

self.as_ref().downcast_raw(id)
}
}
135 changes: 135 additions & 0 deletions tracing/tests/subscriber.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// These tests require the thread-local scoped dispatcher, which only works when
// we have a standard library. The behaviour being tested should be the same
// with the standard lib disabled.
//
// The alternative would be for each of these tests to be defined in a separate
// file, which is :(
#![cfg(feature = "std")]

#[macro_use]
extern crate tracing;
use tracing::{
field::display,
span::{Attributes, Id, Record},
subscriber::{with_default, Interest, Subscriber},
Event, Level, Metadata,
};

mod support;

use self::support::*;

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn event_macros_dont_infinite_loop() {
// This test ensures that an event macro within a subscriber
// won't cause an infinite loop of events.
struct TestSubscriber;
impl Subscriber for TestSubscriber {
fn register_callsite(&self, _: &Metadata<'_>) -> Interest {
// Always return sometimes so that `enabled` will be called
// (which can loop).
Interest::sometimes()
}

fn enabled(&self, meta: &Metadata<'_>) -> bool {
assert!(meta.fields().iter().any(|f| f.name() == "foo"));
event!(Level::TRACE, bar = false);
true
}

fn new_span(&self, _: &Attributes<'_>) -> Id {
Id::from_u64(0xAAAA)
}

fn record(&self, _: &Id, _: &Record<'_>) {}

fn record_follows_from(&self, _: &Id, _: &Id) {}

fn event(&self, event: &Event<'_>) {
assert!(event.metadata().fields().iter().any(|f| f.name() == "foo"));
event!(Level::TRACE, baz = false);
}

fn enter(&self, _: &Id) {}

fn exit(&self, _: &Id) {}
}

with_default(TestSubscriber, || {
event!(Level::TRACE, foo = false);
})
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn boxed_subscriber() {
let (subscriber, handle) = subscriber::mock()
.new_span(
span::mock().named("foo").with_field(
field::mock("bar")
.with_value(&display("hello from my span"))
.only(),
),
)
.enter(span::mock().named("foo"))
.exit(span::mock().named("foo"))
.drop_span(span::mock().named("foo"))
.done()
.run_with_handle();
let subscriber: Box<dyn Subscriber + Send + Sync + 'static> = Box::new(subscriber);

with_default(subscriber, || {
let from = "my span";
let span = span!(
Level::TRACE,
"foo",
bar = format_args!("hello from {}", from)
);
span.in_scope(|| {});
});

handle.assert_finished();
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn arced_subscriber() {
use std::sync::Arc;

let (subscriber, handle) = subscriber::mock()
.new_span(
span::mock().named("foo").with_field(
field::mock("bar")
.with_value(&display("hello from my span"))
.only(),
),
)
.enter(span::mock().named("foo"))
.exit(span::mock().named("foo"))
.drop_span(span::mock().named("foo"))
.event(
event::mock()
.with_fields(field::mock("message").with_value(&display("hello from my event"))),
)
.done()
.run_with_handle();
let subscriber: Arc<dyn Subscriber + Send + Sync + 'static> = Arc::new(subscriber);

// Test using a clone of the `Arc`ed subscriber
with_default(subscriber.clone(), || {
let from = "my span";
let span = span!(
Level::TRACE,
"foo",
bar = format_args!("hello from {}", from)
);
span.in_scope(|| {});
});

with_default(subscriber, || {
tracing::info!("hello from my event");
});

handle.assert_finished();
}

0 comments on commit a4fba84

Please sign in to comment.