graphql_starter/tracing/
common.rs

1use std::{
2    mem,
3    sync::{Arc, Mutex},
4};
5
6use anyhow::Context;
7use tracing::Subscriber;
8use tracing_error::ErrorLayer;
9use tracing_subscriber::{filter::Targets, prelude::*, registry::LookupSpan, reload, Layer, Registry};
10
11use super::MakeWriterInterceptor;
12use crate::error::{GenericErrorCode, MapToErr, Result};
13
14trait FnUpdateTracing: FnMut(&str) -> Result<()> + Send + Sync {}
15impl<T> FnUpdateTracing for T where T: FnMut(&str) -> Result<()> + Send + Sync {}
16
17#[derive(Clone)]
18/// The tracing context.
19///
20/// This context can be cloned cheaply, as it contains an [Arc] inside, and will point to the same context.
21pub struct TracingContext {
22    inner: Arc<Mutex<InnerCtx>>,
23}
24
25/// Inner tracing context for [TracingContext]
26struct InnerCtx {
27    default_filter: String,
28    active_filter: String,
29    fn_update_filter: Box<dyn FnUpdateTracing>,
30}
31
32impl TracingContext {
33    fn new(ctx: InnerCtx) -> Self {
34        Self {
35            inner: Arc::new(Mutex::new(ctx)),
36        }
37    }
38
39    /// Retrieves the default [Targets] filter
40    pub fn default_filter(&self) -> String {
41        let ctx = self.inner.lock().expect("poisoned lock");
42        ctx.default_filter.clone()
43    }
44
45    /// Retrieves the currently active [Targets] filter
46    pub fn active_filter(&self) -> String {
47        let ctx = self.inner.lock().expect("poisoned lock");
48        ctx.active_filter.clone()
49    }
50
51    /// Updates the active [Targets] filter, returning the previously active one
52    pub fn update_active_filter(&mut self, new_filter: impl Into<String>) -> Result<String> {
53        let new_filter = new_filter.into();
54        let mut ctx = self.inner.lock().expect("poisoned lock");
55
56        ctx.fn_update_filter.as_mut()(&new_filter)?;
57        Ok(mem::replace(&mut ctx.active_filter, new_filter))
58    }
59
60    /// Updates the default [Targets] filter, returning the previously default filter
61    pub fn update_default_filter(&mut self, new_default_filter: impl Into<String>) -> String {
62        let new_default_filter = new_default_filter.into();
63        let mut ctx = self.inner.lock().expect("poisoned lock");
64
65        mem::replace(&mut ctx.default_filter, new_default_filter)
66    }
67}
68
69/// Initializes the global tracing subscriber
70///
71/// **A global tracing subscriber can be set only once and will panic if already set**
72pub fn initialize_tracing<L>(layer: L)
73where
74    L: Layer<Registry> + Send + Sync,
75{
76    // Initialize subscriber
77    tracing_subscriber::registry().with(layer).init();
78}
79
80/// Generates the default compact tracing layer with the given [Targets] filter
81pub fn tracing_layer<T>(filter: impl AsRef<str>) -> anyhow::Result<impl Layer<T>>
82where
83    T: Subscriber,
84    for<'span> T: LookupSpan<'span>,
85{
86    // Build the layer
87    let layer = tracing_subscriber::fmt::layer().compact().with_filter(
88        filter
89            .as_ref()
90            .parse::<Targets>()
91            .context("Couldn't parse tracing filter")?,
92    );
93
94    // Return the layer
95    Ok(ErrorLayer::default().and_then(layer))
96}
97
98/// Generates a default tracing layer with the given [Targets] filter that can be updated from its [TracingContext]
99pub fn dynamic_tracing_layer<T>(filter: impl Into<String>) -> anyhow::Result<(impl Layer<T>, TracingContext)>
100where
101    T: Subscriber,
102    for<'span> T: LookupSpan<'span>,
103{
104    let filter = filter.into();
105
106    // Build the layer and handler
107    let (layer, handler) = reload::Layer::new(
108        tracing_subscriber::fmt::layer()
109            .compact()
110            .with_filter(filter.parse::<Targets>().context("Couldn't parse tracing filter")?),
111    );
112
113    // Build the context
114    let context = TracingContext::new(InnerCtx {
115        default_filter: filter.clone(),
116        active_filter: filter,
117        fn_update_filter: Box::new(move |new_filter| {
118            let new_filter = new_filter
119                .parse::<Targets>()
120                .map_to_err_with(GenericErrorCode::BadRequest, "Couldn't parse the filter")?;
121            handler
122                .modify(|layer| *layer.filter_mut() = new_filter)
123                .map_to_internal_err("Couldn't update tracing filter")
124        }),
125    });
126
127    // Return the layer and its context
128    Ok((ErrorLayer::default().and_then(layer), context))
129}
130
131/// Generates a default tracing layer with the given [Targets] filter that can be intercepted with its
132/// [MakeWriterInterceptor]
133pub fn intercepted_tracing_layer<T>(
134    filter: impl AsRef<str>,
135    accumulate: usize,
136    stream_buffer: usize,
137) -> anyhow::Result<(impl Layer<T>, MakeWriterInterceptor)>
138where
139    T: Subscriber,
140    for<'span> T: LookupSpan<'span>,
141{
142    // Build the interceptor writer
143    let make_writer = MakeWriterInterceptor::new(accumulate, stream_buffer);
144
145    // Build the layer
146    let layer = tracing_subscriber::fmt::layer()
147        .compact()
148        .with_writer(make_writer.clone())
149        .with_filter(
150            filter
151                .as_ref()
152                .parse::<Targets>()
153                .context("Couldn't parse tracing filter")?,
154        );
155
156    // Return the layer
157    Ok((ErrorLayer::default().and_then(layer), make_writer))
158}
159
160/// Generates a default tracing layer with the given [Targets] filter that can be updated from its [TracingContext] and
161/// intercepted with its [MakeWriterInterceptor]
162pub fn dynamic_intercepted_tracing_layer<T>(
163    filter: impl Into<String>,
164    accumulate: usize,
165    stream_buffer: usize,
166) -> anyhow::Result<(impl Layer<T>, TracingContext, MakeWriterInterceptor)>
167where
168    T: Subscriber,
169    for<'span> T: LookupSpan<'span>,
170{
171    let filter = filter.into();
172
173    // Build the interceptor writer
174    let make_writer = MakeWriterInterceptor::new(accumulate, stream_buffer);
175
176    // Build the intercepted layer and handler
177    let (layer, handler) = reload::Layer::new(
178        tracing_subscriber::fmt::layer()
179            .compact()
180            .with_writer(make_writer.clone())
181            .with_filter(filter.parse::<Targets>().context("Couldn't parse tracing filter")?),
182    );
183
184    // Build the context
185    let context = TracingContext::new(InnerCtx {
186        default_filter: filter.clone(),
187        active_filter: filter,
188        fn_update_filter: Box::new(move |new_filter| {
189            let new_filter = new_filter
190                .parse::<Targets>()
191                .map_to_err_with(GenericErrorCode::BadRequest, "Couldn't parse the filter")?;
192            handler
193                .modify(|layer| *layer.filter_mut() = new_filter)
194                .map_to_internal_err("Couldn't update tracing filter")
195        }),
196    });
197
198    // Return the layer and its context
199    Ok((ErrorLayer::default().and_then(layer), context, make_writer))
200}