captains_log/
tracing_bridge.rs

1//! # Tracing support
2//!
3//! If you want to log tracing events (either in your code or 3rd-party crate), just enable the **`tracing` feature**.
4//!
5//! The message from tracing will use the same log format as defined in [crate::LogFormat].
6//!
7//! As we have re-exported required stuff from tracing & tracing_subscriber, you don't need to add them as dependency.
8//!
9//! **NOTE**: You should **opt out tracing-log**
10//!  (Because it's in default feature-flag of `tracing_subscriber`, it will prevent us to setup captains_log)
11//!
12//! ## Set global dispatcher (recommended)
13//!
14//! Just turn on the flag `tracing_global` in [crate::Builder], then it will setup [CaptainsLogLayer] with [TracingText] as the
15//! global default.
16//!
17//! Error will be thrown by build() if other default subscribe has been set in tracing.
18//!
19//! ``` rust
20//! use captains_log::*;
21//! recipe::raw_file_logger("/tmp/mylog.log", Level::Debug)
22//!                     .tracing_global()
23//!                    .build().expect("setup log");
24//! ```
25//!
26//! ## Stacking layers (alternative)
27//!
28//! you can choose this method when you need more customization,
29//! or when you need 3rd-party layer implementation.
30//!
31//! The following example skip the log of span enter and span exit, and setup the default
32//! [TracingText] formatter.
33//! Alternatively, you can re-implement [TracingFormatter] trait to change the format of attributes.
34//!
35//! ```
36//! use captains_log::{*, tracing_bridge::*};
37//! // we have re-export tracing_subscriber::{registry, prelude::*},
38//! // and tracing::{dispatch, Dispatcher}
39//! use tracing_subscriber::fmt;
40//! let logger = recipe::raw_file_logger("/tmp/tracing.log", Level::Trace)
41//!                     .build().expect("setup logger");
42//! let layer = logger.tracing_layer::<TracingText>().unwrap()
43//!                 .disable_enter().disable_exit();
44//! // fmt::layer is optional, just to show an example.
45//! let reg = registry().with(fmt::layer().with_writer(std::io::stdout)).with(layer);
46//! dispatcher::set_global_default(Dispatch::new(reg)).expect("init tracing");
47//! ```
48//!
49//! ## Subscribe to tracing in the scope (rarely used).
50//!
51//! Assume you have a different tracing global dispatcher,
52//! and want to output to captains_log only in the scope.
53//! ```
54//! use captains_log::{*, tracing_bridge::*};
55//! use tracing_subscriber::fmt;
56//!
57//! let logger = recipe::raw_file_logger("/tmp/tracing.log", Level::Trace)
58//!                     .build().expect("setup logger");
59//! let reg = registry().with(fmt::layer().with_writer(std::io::stdout));
60//! dispatcher::set_global_default(Dispatch::new(reg)).expect("init tracing");
61//! tracing::trace!("trace with tracing {:?}", true);
62//! let log_dispatch = logger.tracing_dispatch::<TracingText>().unwrap();
63//! dispatcher::with_default(&log_dispatch, || {
64//!     tracing::info!("log from tracing in a scope");
65//! });
66//! ```
67
68use crate::log::Log;
69use crate::log_impl::GlobalLogger;
70use log::Record;
71use std::fmt::{self, Write};
72use tracing::field::{Field, Visit};
73pub use tracing::{dispatcher, Dispatch};
74use tracing::{span, Event, Metadata, Subscriber};
75use tracing_subscriber::layer::{Context, Layer};
76use tracing_subscriber::registry::LookupSpan;
77pub use tracing_subscriber::{prelude::*, registry};
78
79/// An tracing-subscriber layer implementation, for capturing event from tracing
80pub struct CaptainsLogLayer<F = TracingText>
81where
82    F: TracingFormatter,
83{
84    /// This is a cycle pointer to the parent, to be filled after initialization.
85    logger: &'static GlobalLogger,
86    disable_span_new: bool,
87    disable_record: bool,
88    disable_enter: bool,
89    disable_exit: bool,
90    disable_close: bool,
91    _phan: F,
92}
93
94unsafe impl<F: TracingFormatter> Send for CaptainsLogLayer<F> {}
95unsafe impl<F: TracingFormatter> Sync for CaptainsLogLayer<F> {}
96
97macro_rules! log_span {
98    ($logger: expr, $id: expr, $meta: expr, $action: expr, $v: expr) => {{
99        let msg = $v.as_ref();
100        if msg.len() == 0 {
101            $logger.log(
102                &Record::builder()
103                    .level(convert_tracing_level($meta.level()))
104                    .target($meta.target())
105                    .module_path($meta.module_path())
106                    .file($meta.file())
107                    .line($meta.line())
108                    .args(format_args!("span({}) {}", $id.into_u64(), $action))
109                    .build(),
110            );
111        } else {
112            $logger.log(
113                &Record::builder()
114                    .level(convert_tracing_level($meta.level()))
115                    .target($meta.target())
116                    .module_path($meta.module_path())
117                    .file($meta.file())
118                    .line($meta.line())
119                    .args(format_args!("span({}) {}: {}", $id.into_u64(), $action, msg))
120                    .build(),
121            );
122        }
123    }};
124}
125
126impl<F> CaptainsLogLayer<F>
127where
128    F: TracingFormatter,
129{
130    #[inline(always)]
131    pub(crate) fn new(logger: &'static GlobalLogger) -> Self {
132        Self {
133            logger,
134            _phan: Default::default(),
135            disable_span_new: false,
136            disable_record: false,
137            disable_enter: false,
138            disable_exit: false,
139            disable_close: false,
140        }
141    }
142
143    #[inline]
144    pub fn disable_enter(mut self) -> Self {
145        self.disable_enter = true;
146        self
147    }
148
149    #[inline]
150    pub fn disable_exit(mut self) -> Self {
151        self.disable_exit = true;
152        self
153    }
154
155    #[inline]
156    pub fn disable_close(mut self) -> Self {
157        self.disable_close = true;
158        self
159    }
160
161    #[inline]
162    pub fn disable_record(mut self) -> Self {
163        self.disable_record = true;
164        self
165    }
166
167    #[inline]
168    pub fn disable_span(mut self) -> Self {
169        self.disable_span_new = true;
170        self.disable_record = true;
171        self.disable_enter = true;
172        self.disable_exit = true;
173        self.disable_close = true;
174        self
175    }
176}
177
178impl<S, F> Layer<S> for CaptainsLogLayer<F>
179where
180    S: Subscriber + for<'a> LookupSpan<'a>,
181    F: TracingFormatter + 'static,
182{
183    #[inline(always)]
184    fn enabled(&self, meta: &Metadata<'_>, _ctx: Context<'_, S>) -> bool {
185        convert_tracing_level(meta.level()) <= log::STATIC_MAX_LEVEL
186    }
187
188    #[inline]
189    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
190        if self.disable_span_new {
191            return;
192        }
193        let data = ctx.span(id).expect("Span not found");
194        let meta = data.metadata();
195        let mut extensions = data.extensions_mut();
196        if extensions.get_mut::<F>().is_none() {
197            let mut v = F::default();
198            attrs.record(&mut v);
199            log_span!(self.logger, id, meta, "new", v);
200            extensions.insert(v);
201        }
202    }
203
204    #[inline]
205    fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
206        if self.disable_span_new {
207            return;
208        }
209        let data = ctx.span(id).expect("Span not found");
210        let meta = data.metadata();
211        let mut extensions = data.extensions_mut();
212        if let Some(v) = extensions.get_mut::<F>() {
213            values.record(v);
214            if !self.disable_record {
215                log_span!(self.logger, id, meta, "record", v);
216            }
217        } else {
218            let mut v = F::default();
219            values.record(&mut v);
220            if !self.disable_record {
221                log_span!(self.logger, id, meta, "record", v);
222            }
223            extensions.insert(v);
224        }
225    }
226
227    #[inline]
228    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
229        if self.disable_enter {
230            return;
231        }
232        let data = ctx.span(&id).expect("Span not found, this is a bug");
233        let meta = data.metadata();
234        let extensions = data.extensions();
235        if let Some(v) = extensions.get::<F>() {
236            log_span!(self.logger, id, meta, "enter", v);
237        }
238    }
239
240    #[inline]
241    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
242        if self.disable_exit {
243            return;
244        }
245        let data = ctx.span(&id).expect("Span not found, this is a bug");
246        let meta = data.metadata();
247        let extensions = data.extensions();
248        if let Some(v) = extensions.get::<F>() {
249            log_span!(self.logger, id, meta, "exit", v);
250        }
251    }
252
253    #[inline]
254    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
255        if self.disable_close {
256            return;
257        }
258        let data = ctx.span(&id).expect("Span not found, this is a bug");
259        let meta = data.metadata();
260        let extensions = data.extensions();
261        if let Some(v) = extensions.get::<F>() {
262            log_span!(self.logger, id, meta, "close", v);
263        }
264    }
265
266    #[inline(always)]
267    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
268        let meta = event.metadata();
269        let mut v = F::default();
270        event.record(&mut v);
271        self.logger.log(
272            &Record::builder()
273                .level(convert_tracing_level(meta.level()))
274                .args(format_args!("{}", v.as_ref()))
275                .target(meta.target())
276                .module_path(meta.module_path())
277                .file(meta.file())
278                .line(meta.line())
279                .build(),
280        );
281    }
282}
283
284/// To convert tracing::Level to log::Level
285#[inline(always)]
286pub fn convert_tracing_level(level: &tracing::Level) -> log::Level {
287    match *level {
288        tracing::Level::TRACE => log::Level::Trace,
289        tracing::Level::DEBUG => log::Level::Debug,
290        tracing::Level::INFO => log::Level::Info,
291        tracing::Level::WARN => log::Level::Warn,
292        tracing::Level::ERROR => log::Level::Error,
293    }
294}
295
296/// Format span::Attributes, keep track of span::record() adding attributes
297pub trait TracingFormatter: Visit + Default + AsRef<str> + Send + Sync + 'static {}
298
299/// Format span::Attributes into Text
300pub struct TracingText(String);
301
302impl Visit for TracingText {
303    fn record_str(&mut self, field: &Field, value: &str) {
304        if self.0.len() == 0 {
305            if field.name() == "message" {
306                write!(self.0, "{}", value).unwrap();
307                return;
308            } else {
309                write!(self.0, "{}={}", field.name(), value).unwrap();
310            }
311        } else {
312            write!(self.0, ", {}={}", field.name(), value).unwrap();
313        }
314    }
315
316    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
317        if self.0.len() == 0 {
318            if field.name() == "message" {
319                write!(self.0, "{:?}", value).unwrap();
320            } else {
321                write!(self.0, "{}={:?}", field.name(), value).unwrap();
322            }
323        } else {
324            write!(self.0, ", {}={:?}", field.name(), value).unwrap();
325        }
326    }
327}
328
329impl Default for TracingText {
330    #[inline(always)]
331    fn default() -> Self {
332        Self(String::new())
333    }
334}
335
336impl AsRef<str> for TracingText {
337    #[inline(always)]
338    fn as_ref(&self) -> &str {
339        self.0.as_str()
340    }
341}
342
343impl TracingFormatter for TracingText {}