Skip to main content

msg_common/
span.rs

1use std::{
2    pin::Pin,
3    task::{Context, Poll},
4};
5
6use derive_more::{Deref, DerefMut};
7
8/// A container with a [`tracing::Span`] attached.
9///
10/// If `T` is a `Future`, the span will be entered when polling the future, making this struct
11/// almost equivalent to [`tracing::Instrument`]. The main difference is that the return value will
12/// contain the span as well so it can be re-entered later.
13#[derive(Debug, Clone, Deref, DerefMut)]
14pub struct WithSpan<T> {
15    #[deref]
16    #[deref_mut]
17    pub inner: T,
18    pub span: tracing::Span,
19}
20
21impl<T> WithSpan<T> {
22    /// Create a spanned container using [`tracing::Span::current`] span.
23    #[inline]
24    pub fn current(inner: T) -> Self {
25        Self { inner, span: tracing::Span::current() }
26    }
27
28    /// Create a container with a no-op [`tracing::Span::none`] span, to be eventually replaced
29    /// with [`Self::with_span`]
30    #[inline]
31    pub fn new(inner: T) -> Self {
32        Self { inner, span: tracing::Span::none() }
33    }
34
35    /// Replace the current [`tracing::Span`] with the provided one.
36    #[inline]
37    pub fn with_span(mut self, span: tracing::Span) -> Self {
38        self.span = span;
39        self
40    }
41
42    /// Break the spanned container into a tuple containing the inner object and the span.
43    #[inline]
44    pub fn into_parts(self) -> (T, tracing::Span) {
45        (self.inner, self.span)
46    }
47
48    #[inline]
49    pub fn into_inner(self) -> T {
50        self.inner
51    }
52}
53
54/// Projection type for [`WithSpan`], as generated by a library like `pin_project`.
55struct WithSpanProjection<'a, T> {
56    inner: Pin<&'a mut T>,
57    // For the purpose of `poll`, we don't need a mutable reference.
58    span: &'a tracing::Span,
59}
60
61/// The struct is [`Unpin`] only if its pinned fields are [`Unpin`].
62impl<T: Unpin> Unpin for WithSpan<T> {}
63
64impl<T> WithSpan<T> {
65    /// Project the fields of the struct.
66    #[inline]
67    fn project(self: Pin<&mut Self>) -> WithSpanProjection<'_, T> {
68        unsafe {
69            // SAFETY: span is [`Unpin`], and self.inner is never moved while pinned.
70            let this = self.get_unchecked_mut();
71            WithSpanProjection { inner: Pin::new_unchecked(&mut this.inner), span: &mut this.span }
72        }
73    }
74}
75
76impl<T: Future> Future for WithSpan<T> {
77    type Output = WithSpan<T::Output>;
78
79    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
80        let WithSpanProjection { inner, span } = self.project();
81
82        let _g = span.enter();
83
84        if let Poll::Ready(val) = inner.poll(cx) {
85            return Poll::Ready(val.with_span(span));
86        }
87
88        Poll::Pending
89    }
90}
91
92/// A container with a [`tracing::span::EnteredSpan`] attached.
93#[derive(Debug, Deref, DerefMut)]
94pub struct WithEntered<T> {
95    #[deref]
96    #[deref_mut]
97    pub inner: T,
98    pub span: tracing::span::EnteredSpan,
99}
100
101/// Trait to convert [`WithSpan<T>`] containers into [`WithEntered<T>`] containers, by
102/// [`tracing::Span::enter`]ing the span.
103///
104/// The associated type [`EnterSpan::Output`] allows for conversions that change the outer type
105/// which may wrap [`WithSpan<T>`], such as `Option` or `Poll`. For example, one implementation for
106/// poll could allow to convert from `Poll<WithSpan<T>>` to `Poll<WithEntered<T>>`.
107pub trait EnterSpan {
108    type Output;
109
110    fn enter(self) -> Self::Output;
111}
112
113impl<T> EnterSpan for WithSpan<T> {
114    type Output = WithEntered<T>;
115
116    #[inline]
117    fn enter(self) -> Self::Output {
118        let WithSpan { inner, span } = self;
119        WithEntered { inner, span: span.entered() }
120    }
121}
122
123impl<T> EnterSpan for WithEntered<T> {
124    type Output = WithEntered<T>;
125
126    #[inline]
127    fn enter(self) -> Self::Output {
128        self
129    }
130}
131
132impl<T: EnterSpan> EnterSpan for Option<T> {
133    type Output = Option<<T as EnterSpan>::Output>;
134
135    #[inline]
136    fn enter(self) -> Self::Output {
137        self.map(|v| v.enter())
138    }
139}
140
141impl<T: EnterSpan, E> EnterSpan for Result<T, E> {
142    type Output = Result<<T as EnterSpan>::Output, E>;
143
144    #[inline]
145    fn enter(self) -> Self::Output {
146        self.map(|v| v.enter())
147    }
148}
149
150impl<T: EnterSpan> EnterSpan for Poll<T> {
151    type Output = Poll<<T as EnterSpan>::Output>;
152
153    #[inline]
154    fn enter(self) -> Self::Output {
155        match self {
156            Poll::Ready(v) => Poll::Ready(v.enter()),
157            Poll::Pending => Poll::Pending,
158        }
159    }
160}
161
162pub trait IntoSpanExt {
163    fn into_span(self) -> tracing::Span;
164}
165
166impl IntoSpanExt for tracing::Span {
167    #[inline]
168    fn into_span(self) -> tracing::Span {
169        self
170    }
171}
172
173impl IntoSpanExt for &tracing::Span {
174    #[inline]
175    fn into_span(self) -> tracing::Span {
176        self.clone()
177    }
178}
179
180impl IntoSpanExt for tracing::span::EnteredSpan {
181    #[inline]
182    fn into_span(self) -> tracing::Span {
183        self.clone()
184    }
185}
186
187pub trait SpanExt<T> {
188    fn with_span(self, span: impl IntoSpanExt) -> WithSpan<T>;
189
190    fn with_current_span(self) -> WithSpan<T>;
191}
192
193impl<T> SpanExt<T> for T {
194    #[inline]
195    fn with_span(self, span: impl IntoSpanExt) -> WithSpan<T> {
196        WithSpan { inner: self, span: span.into_span() }
197    }
198
199    fn with_current_span(self) -> WithSpan<T> {
200        WithSpan::current(self)
201    }
202}