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