trace-core: Add overrideable downcasting to Subscribers (#974)

## Motivation

In order to implement "out of band" `Subscriber` APIs in third-party
subscriber implementations (see [this comment]) users may want to 
downcast the current `Dispatch` to a concrete subscriber type.

For example, in a library for integrating `tokio-trace` with a fancy new
(hypothetical) distributed tracing technology "ElizaTracing", which uses
256-bit span IDs, we might expect to see a function like this:
```rust

pub fn correlate(tt: tokio_trace::span::Id, et: elizatracing::SpanId) {
    tokio_trace::dispatcher::with(|c| {
        if let Some(s) = c.downcast_ref::<elizatracing::Subscriber>() {
            s.do_elizatracing_correlation_magic(tt, et);
        }
    }); 
}
```

This allows users to correlate `tokio-trace` IDs with IDs in the
distributed tracing system without having to pass a special handle to
the subscriber through application code (as one is already present in
thread-local storage, but with its type erased).

## Solution

This branch makes the following changes:
 * Add an object-safe `downcast_raw` method to the `Subscriber` trait,
   taking a `TypeId` and returning an `*const ()` if the type ID 
   matches the subscriber's type ID, or `None` if it does not, and
 * Add `is<T>` and `downcast_ref<T>` functions to `Subscriber` 
   and `Dispatch`, using `downcast_raw`.

Unlike the approach implemented in #950, the `downcast_raw` method is
object-safe, since it takes a `TypeId` rather than a type _parameter_ 
and returns a void pointer rather than an `&T`. This means that
`Subscriber` implementations can override this method if necessary. For
example, a `Subscriber` that fans out to multiple component subscribers
can downcast to their component parts, and "chained" or "middleware"
subscribers, which wrap an inner `Subscriber` and modify its behaviour 
somehow, can downcast to the inner type if they choose to.

[this comment]: https://github.com/tokio-rs/tokio/issues/932#issuecomment-469473501
[`std::error::Error`'s]: https://doc.rust-lang.org/1.33.0/src/std/error.rs.html#204

Refs: #950, #953, https://github.com/tokio-rs/tokio/issues/948#issuecomment-469444293

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Eliza Weisman 2019-03-22 16:21:46 -07:00 committed by GitHub
parent 30330da11a
commit 9c5cad037f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 0 deletions

View File

@ -6,6 +6,7 @@ use {
};
use std::{
any::Any,
cell::RefCell,
fmt,
sync::{Arc, Weak},
@ -226,6 +227,20 @@ impl Dispatch {
pub fn drop_span(&self, id: span::Id) {
self.subscriber.drop_span(id)
}
/// Returns `true` if this `Dispatch` forwards to a `Subscriber` of type
/// `T`.
#[inline]
pub fn is<T: Any>(&self) -> bool {
Subscriber::is::<T>(&*self.subscriber)
}
/// Returns some reference to the `Subscriber` this `Dispatch` forwards to
/// if it is of type `T`, or `None` if it isn't.
#[inline]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
Subscriber::downcast_ref(&*self.subscriber)
}
}
impl fmt::Debug for Dispatch {
@ -275,3 +290,20 @@ impl Registrar {
self.0.upgrade().map(|s| s.register_callsite(metadata))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn dispatch_is() {
let dispatcher = Dispatch::new(NoSubscriber);
assert!(dispatcher.is::<NoSubscriber>());
}
#[test]
fn dispatch_downcasts() {
let dispatcher = Dispatch::new(NoSubscriber);
assert!(dispatcher.downcast_ref::<NoSubscriber>().is_some());
}
}

View File

@ -1,6 +1,11 @@
//! Subscribers collect and record trace data.
use {span, Event, Metadata};
use std::{
any::{Any, TypeId},
ptr,
};
/// Trait representing the functions required to collect trace data.
///
/// Crates that provide implementations of methods for collecting or recording
@ -262,6 +267,61 @@ pub trait Subscriber: 'static {
fn drop_span(&self, id: span::Id) {
let _ = id;
}
// === Downcasting methods ================================================
/// If `self` is the same type as the provided `TypeId`, returns an untyped
/// `*const` pointer to that type. Otherwise, returns `None`.
///
/// If you wish to downcast a `Subscriber`, it is strongly advised to use
/// the safe API provided by [`downcast_ref`] instead.
///
/// This API is required for `downcast_raw` to be a trait method; a method
/// signature like [`downcast_ref`] (with a generic type parameter) is not
/// object-safe, and thus cannot be a trait method for `Subscriber`. This
/// means that if we only exposed `downcast_ref`, `Subscriber`
/// implementations could not override the downcasting behavior
///
/// This method may be overridden by "fan out" or "chained" subscriber
/// implementations which consist of multiple composed types. Such
/// subscribers might allow `downcast_raw` by returning references to those
/// component if they contain components with the given `TypeId`.
///
/// # Safety
///
/// The [`downcast_ref`] method expects that the pointer returned by
/// `downcast_raw` is non-null and points to a valid instance of the type
/// with the provided `TypeId`. Failure to ensure this will result in
/// undefined behaviour, so implementing `downcast_raw` is unsafe.
///
/// [`downcast_ref`]: #method.downcast_ref
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
if id == TypeId::of::<Self>() {
Some(self as *const Self as *const ())
} else {
None
}
}
}
impl Subscriber {
/// Returns `true` if this `Subscriber` is the same type as `T`.
pub fn is<T: Any>(&self) -> bool {
self.downcast_ref::<T>().is_some()
}
/// Returns some reference to this `Subscriber` value if it is of type `T`,
/// or `None` if it isn't.
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
unsafe {
let raw = self.downcast_raw(TypeId::of::<T>())?;
if raw == ptr::null() {
None
} else {
Some(&*(raw as *const _))
}
}
}
}
/// Indicates a [`Subscriber`]'s interest in a particular callsite.