pub use tracing_core::span::{Attributes, Id, Record};
use crate::stdlib::{
cmp, fmt,
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::Deref,
};
use crate::{
dispatcher::{self, Dispatch},
field, Metadata,
};
pub trait AsId: crate::sealed::Sealed {
fn as_id(&self) -> Option<&Id>;
}
#[derive(Clone)]
pub struct Span {
inner: Option<Inner>,
meta: Option<&'static Metadata<'static>>,
}
#[derive(Debug)]
pub(crate) struct Inner {
id: Id,
subscriber: Dispatch,
}
#[derive(Debug)]
#[must_use = "once a span has been entered, it should be exited"]
pub struct Entered<'a> {
span: &'a Span,
}
#[derive(Debug)]
#[must_use = "once a span has been entered, it should be exited"]
pub struct EnteredSpan {
span: Span,
_not_send: PhantomNotSend,
}
#[cfg(feature = "log")]
const LIFECYCLE_LOG_TARGET: &str = "tracing::span";
#[cfg(feature = "log")]
const ACTIVITY_LOG_TARGET: &str = "tracing::span::active";
impl Span {
pub fn new(meta: &'static Metadata<'static>, values: &field::ValueSet<'_>) -> Span {
dispatcher::get_default(|dispatch| Self::new_with(meta, values, dispatch))
}
#[inline]
#[doc(hidden)]
pub fn new_with(
meta: &'static Metadata<'static>,
values: &field::ValueSet<'_>,
dispatch: &Dispatch,
) -> Span {
let new_span = Attributes::new(meta, values);
Self::make_with(meta, new_span, dispatch)
}
pub fn new_root(meta: &'static Metadata<'static>, values: &field::ValueSet<'_>) -> Span {
dispatcher::get_default(|dispatch| Self::new_root_with(meta, values, dispatch))
}
#[inline]
#[doc(hidden)]
pub fn new_root_with(
meta: &'static Metadata<'static>,
values: &field::ValueSet<'_>,
dispatch: &Dispatch,
) -> Span {
let new_span = Attributes::new_root(meta, values);
Self::make_with(meta, new_span, dispatch)
}
pub fn child_of(
parent: impl Into<Option<Id>>,
meta: &'static Metadata<'static>,
values: &field::ValueSet<'_>,
) -> Span {
let mut parent = parent.into();
dispatcher::get_default(move |dispatch| {
Self::child_of_with(Option::take(&mut parent), meta, values, dispatch)
})
}
#[inline]
#[doc(hidden)]
pub fn child_of_with(
parent: impl Into<Option<Id>>,
meta: &'static Metadata<'static>,
values: &field::ValueSet<'_>,
dispatch: &Dispatch,
) -> Span {
let new_span = match parent.into() {
Some(parent) => Attributes::child_of(parent, meta, values),
None => Attributes::new_root(meta, values),
};
Self::make_with(meta, new_span, dispatch)
}
#[inline(always)]
pub fn new_disabled(meta: &'static Metadata<'static>) -> Span {
Self {
inner: None,
meta: Some(meta),
}
}
#[inline(always)]
pub const fn none() -> Span {
Self {
inner: None,
meta: None,
}
}
pub fn current() -> Span {
dispatcher::get_default(|dispatch| {
if let Some((id, meta)) = dispatch.current_span().into_inner() {
let id = dispatch.clone_span(&id);
Self {
inner: Some(Inner::new(id, dispatch)),
meta: Some(meta),
}
} else {
Self::none()
}
})
}
fn make_with(
meta: &'static Metadata<'static>,
new_span: Attributes<'_>,
dispatch: &Dispatch,
) -> Span {
let attrs = &new_span;
let id = dispatch.new_span(attrs);
let inner = Some(Inner::new(id, dispatch));
let span = Self {
inner,
meta: Some(meta),
};
if_log_enabled! { *meta.level(), {
let target = if attrs.is_empty() {
LIFECYCLE_LOG_TARGET
} else {
meta.target()
};
let values = attrs.values();
span.log(
target,
level_to_log!(*meta.level()),
format_args!("++ {};{}", meta.name(), crate::log::LogValueSet { values, is_first: false }),
);
}}
span
}
#[inline(always)]
pub fn enter(&self) -> Entered<'_> {
self.do_enter();
Entered { span: self }
}
#[inline(always)]
pub fn entered(self) -> EnteredSpan {
self.do_enter();
EnteredSpan {
span: self,
_not_send: PhantomNotSend,
}
}
pub fn or_current(self) -> Self {
if self.is_disabled() {
return Self::current();
}
self
}
#[inline(always)]
fn do_enter(&self) {
if let Some(inner) = self.inner.as_ref() {
inner.subscriber.enter(&inner.id);
}
if_log_enabled! { crate::Level::TRACE, {
if let Some(_meta) = self.meta {
self.log(ACTIVITY_LOG_TARGET, log::Level::Trace, format_args!("-> {};", _meta.name()));
}
}}
}
#[inline(always)]
fn do_exit(&self) {
if let Some(inner) = self.inner.as_ref() {
inner.subscriber.exit(&inner.id);
}
if_log_enabled! { crate::Level::TRACE, {
if let Some(_meta) = self.meta {
self.log(ACTIVITY_LOG_TARGET, log::Level::Trace, format_args!("<- {};", _meta.name()));
}
}}
}
pub fn in_scope<F: FnOnce() -> T, T>(&self, f: F) -> T {
let _enter = self.enter();
f()
}
pub fn field<Q: ?Sized>(&self, field: &Q) -> Option<field::Field>
where
Q: field::AsField,
{
self.metadata().and_then(|meta| field.as_field(meta))
}
#[inline]
pub fn has_field<Q: ?Sized>(&self, field: &Q) -> bool
where
Q: field::AsField,
{
self.field(field).is_some()
}
pub fn record<Q: ?Sized, V>(&self, field: &Q, value: V) -> &Self
where
Q: field::AsField,
V: field::Value,
{
if let Some(meta) = self.meta {
if let Some(field) = field.as_field(meta) {
self.record_all(
&meta
.fields()
.value_set(&[(&field, Some(&value as &dyn field::Value))]),
);
}
}
self
}
pub fn record_all(&self, values: &field::ValueSet<'_>) -> &Self {
let record = Record::new(values);
if let Some(ref inner) = self.inner {
inner.record(&record);
}
if let Some(_meta) = self.meta {
if_log_enabled! { *_meta.level(), {
let target = if record.is_empty() {
LIFECYCLE_LOG_TARGET
} else {
_meta.target()
};
self.log(
target,
level_to_log!(*_meta.level()),
format_args!("{};{}", _meta.name(), crate::log::LogValueSet { values, is_first: false }),
);
}}
}
self
}
#[inline]
pub fn is_disabled(&self) -> bool {
self.inner.is_none()
}
#[inline]
pub fn is_none(&self) -> bool {
self.is_disabled() && self.meta.is_none()
}
pub fn follows_from(&self, from: impl Into<Option<Id>>) -> &Self {
if let Some(ref inner) = self.inner {
if let Some(from) = from.into() {
inner.follows_from(&from);
}
}
self
}
pub fn id(&self) -> Option<Id> {
self.inner.as_ref().map(Inner::id)
}
pub fn metadata(&self) -> Option<&'static Metadata<'static>> {
self.meta
}
#[cfg(feature = "log")]
#[inline]
fn log(&self, target: &str, level: log::Level, message: fmt::Arguments<'_>) {
if let Some(meta) = self.meta {
if level_to_log!(*meta.level()) <= log::max_level() {
let logger = log::logger();
let log_meta = log::Metadata::builder().level(level).target(target).build();
if logger.enabled(&log_meta) {
if let Some(ref inner) = self.inner {
logger.log(
&log::Record::builder()
.metadata(log_meta)
.module_path(meta.module_path())
.file(meta.file())
.line(meta.line())
.args(format_args!("{} span={}", message, inner.id.into_u64()))
.build(),
);
} else {
logger.log(
&log::Record::builder()
.metadata(log_meta)
.module_path(meta.module_path())
.file(meta.file())
.line(meta.line())
.args(message)
.build(),
);
}
}
}
}
}
pub fn with_subscriber<T>(&self, f: impl FnOnce((&Id, &Dispatch)) -> T) -> Option<T> {
self.inner
.as_ref()
.map(|inner| f((&inner.id, &inner.subscriber)))
}
}
impl cmp::PartialEq for Span {
fn eq(&self, other: &Self) -> bool {
match (&self.meta, &other.meta) {
(Some(this), Some(that)) => {
this.callsite() == that.callsite() && self.inner == other.inner
}
_ => false,
}
}
}
impl Hash for Span {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.inner.hash(hasher);
}
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut span = f.debug_struct("Span");
if let Some(meta) = self.meta {
span.field("name", &meta.name())
.field("level", &meta.level())
.field("target", &meta.target());
if let Some(ref inner) = self.inner {
span.field("id", &inner.id());
} else {
span.field("disabled", &true);
}
if let Some(ref path) = meta.module_path() {
span.field("module_path", &path);
}
if let Some(ref line) = meta.line() {
span.field("line", &line);
}
if let Some(ref file) = meta.file() {
span.field("file", &file);
}
} else {
span.field("none", &true);
}
span.finish()
}
}
impl<'a> From<&'a Span> for Option<&'a Id> {
fn from(span: &'a Span) -> Self {
span.inner.as_ref().map(|inner| &inner.id)
}
}
impl<'a> From<&'a Span> for Option<Id> {
fn from(span: &'a Span) -> Self {
span.inner.as_ref().map(Inner::id)
}
}
impl From<Span> for Option<Id> {
fn from(span: Span) -> Self {
span.inner.as_ref().map(Inner::id)
}
}
impl<'a> From<&'a EnteredSpan> for Option<&'a Id> {
fn from(span: &'a EnteredSpan) -> Self {
span.inner.as_ref().map(|inner| &inner.id)
}
}
impl<'a> From<&'a EnteredSpan> for Option<Id> {
fn from(span: &'a EnteredSpan) -> Self {
span.inner.as_ref().map(Inner::id)
}
}
impl Drop for Span {
#[inline(always)]
fn drop(&mut self) {
if let Some(Inner {
ref id,
ref subscriber,
}) = self.inner
{
subscriber.try_close(id.clone());
}
if_log_enabled! { crate::Level::TRACE, {
if let Some(meta) = self.meta {
self.log(
LIFECYCLE_LOG_TARGET,
log::Level::Trace,
format_args!("-- {};", meta.name()),
);
}
}}
}
}
impl Inner {
fn follows_from(&self, from: &Id) {
self.subscriber.record_follows_from(&self.id, from)
}
fn id(&self) -> Id {
self.id.clone()
}
fn record(&self, values: &Record<'_>) {
self.subscriber.record(&self.id, values)
}
fn new(id: Id, subscriber: &Dispatch) -> Self {
Inner {
id,
subscriber: subscriber.clone(),
}
}
}
impl cmp::PartialEq for Inner {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Hash for Inner {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Clone for Inner {
fn clone(&self) -> Self {
Inner {
id: self.subscriber.clone_span(&self.id),
subscriber: self.subscriber.clone(),
}
}
}
impl EnteredSpan {
pub fn id(&self) -> Option<Id> {
self.inner.as_ref().map(Inner::id)
}
#[inline]
pub fn exit(mut self) -> Span {
let span = mem::replace(&mut self.span, Span::none());
span.do_exit();
span
}
}
impl Deref for EnteredSpan {
type Target = Span;
#[inline]
fn deref(&self) -> &Span {
&self.span
}
}
impl<'a> Drop for Entered<'a> {
#[inline(always)]
fn drop(&mut self) {
self.span.do_exit()
}
}
impl Drop for EnteredSpan {
#[inline(always)]
fn drop(&mut self) {
self.span.do_exit()
}
}
#[derive(Debug)]
struct PhantomNotSend {
ghost: PhantomData<*mut ()>,
}
#[allow(non_upper_case_globals)]
const PhantomNotSend: PhantomNotSend = PhantomNotSend { ghost: PhantomData };
unsafe impl Sync for PhantomNotSend {}
#[cfg(test)]
mod test {
use super::*;
trait AssertSend: Send {}
impl AssertSend for Span {}
trait AssertSync: Sync {}
impl AssertSync for Span {}
impl AssertSync for Entered<'_> {}
impl AssertSync for EnteredSpan {}
#[test]
fn test_record_backwards_compat() {
Span::current().record("some-key", "some text");
Span::current().record("some-key", false);
}
}