zenoh_util/
log.rs

1//
2// Copyright (c) 2024 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14use std::{fmt, thread, thread::ThreadId};
15
16use tracing::{field::Field, span, Event, Metadata, Subscriber};
17use tracing_subscriber::{
18    layer::{Context, SubscriberExt},
19    registry::LookupSpan,
20    EnvFilter,
21};
22
23/// A utility function to enable the tracing formatting subscriber.
24///
25/// The [`tracing_subscriber`]` is initialized from the `RUST_LOG` environment variable.
26/// If `RUST_LOG` is not set, then logging is not enabled.
27///
28/// # Safety
29///
30/// Calling this function initializes a `lazy_static` in the [`tracing`] crate.
31/// Such static is not deallocated prior to process exiting, thus tools such as `valgrind`
32/// will report a memory leak.
33/// Refer to this issue: <https://github.com/tokio-rs/tracing/issues/2069>
34pub fn try_init_log_from_env() {
35    if let Ok(env_filter) = EnvFilter::try_from_default_env() {
36        init_env_filter(env_filter);
37    }
38}
39
40/// A utility function to enable the tracing formatting subscriber.
41///
42/// The [`tracing_subscriber`] is initialized from the `RUST_LOG` environment variable.
43/// If `RUST_LOG` is not set, then fallback directives are used.
44///
45/// # Safety
46/// Calling this function initializes a `lazy_static` in the [`tracing`] crate.
47/// Such static is not deallocated prior to process existing, thus tools such as `valgrind`
48/// will report a memory leak.
49/// Refer to this issue: <https://github.com/tokio-rs/tracing/issues/2069>
50pub fn init_log_from_env_or<S>(fallback: S)
51where
52    S: AsRef<str>,
53{
54    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(fallback));
55    init_env_filter(env_filter);
56}
57
58fn init_env_filter(env_filter: EnvFilter) {
59    let subscriber = tracing_subscriber::fmt()
60        .with_env_filter(env_filter)
61        .with_thread_ids(true)
62        .with_thread_names(true)
63        .with_level(true)
64        .with_target(true);
65
66    let subscriber = subscriber.finish();
67    let _ = tracing::subscriber::set_global_default(subscriber);
68}
69
70pub struct LogRecord {
71    pub target: String,
72    pub level: tracing::Level,
73    pub file: Option<&'static str>,
74    pub line: Option<u32>,
75    pub thread_id: ThreadId,
76    pub thread_name: Option<String>,
77    pub message: Option<String>,
78    pub attributes: Vec<(&'static str, String)>,
79}
80
81#[derive(Clone)]
82struct SpanFields(Vec<(&'static str, String)>);
83
84struct Layer<Enabled, Callback> {
85    enabled: Enabled,
86    callback: Callback,
87}
88
89impl<S, E, C> tracing_subscriber::Layer<S> for Layer<E, C>
90where
91    S: Subscriber + for<'a> LookupSpan<'a>,
92    E: Fn(&Metadata) -> bool + 'static,
93    C: Fn(LogRecord) + 'static,
94{
95    fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
96        (self.enabled)(metadata)
97    }
98
99    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
100        let span = ctx.span(id).unwrap();
101        let mut extensions = span.extensions_mut();
102        let mut fields = vec![];
103        attrs.record(&mut |field: &Field, value: &dyn fmt::Debug| {
104            fields.push((field.name(), format!("{value:?}")))
105        });
106        extensions.insert(SpanFields(fields));
107    }
108
109    fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
110        let span = ctx.span(id).unwrap();
111        let mut extensions = span.extensions_mut();
112        let fields = extensions.get_mut::<SpanFields>().unwrap();
113        values.record(&mut |field: &Field, value: &dyn fmt::Debug| {
114            fields.0.push((field.name(), format!("{value:?}")))
115        });
116    }
117
118    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
119        let thread = thread::current();
120        let mut record = LogRecord {
121            target: event.metadata().target().into(),
122            level: *event.metadata().level(),
123            file: event.metadata().file(),
124            line: event.metadata().line(),
125            thread_id: thread.id(),
126            thread_name: thread.name().map(Into::into),
127            message: None,
128            attributes: vec![],
129        };
130        if let Some(scope) = ctx.event_scope(event) {
131            for span in scope.from_root() {
132                let extensions = span.extensions();
133                let fields = extensions.get::<SpanFields>().unwrap();
134                record.attributes.extend(fields.0.iter().cloned());
135            }
136        }
137        event.record(&mut |field: &Field, value: &dyn fmt::Debug| {
138            if field.name() == "message" {
139                record.message = Some(format!("{value:?}"));
140            } else {
141                record.attributes.push((field.name(), format!("{value:?}")))
142            }
143        });
144        (self.callback)(record);
145    }
146}
147
148pub fn init_log_with_callback(
149    enabled: impl Fn(&Metadata) -> bool + Send + Sync + 'static,
150    callback: impl Fn(LogRecord) + Send + Sync + 'static,
151) {
152    let subscriber = tracing_subscriber::registry().with(Layer { enabled, callback });
153    let _ = tracing::subscriber::set_global_default(subscriber);
154}
155
156#[cfg(feature = "test")]
157// Used to verify memory leaks for valgrind CI.
158// `EnvFilter` internally uses a static reference that is not cleaned up yielding to false positive in valgrind.
159// This function enables logging without calling `EnvFilter` for env configuration.
160pub fn init_log_test() {
161    let subscriber = tracing_subscriber::fmt()
162        .with_max_level(tracing::Level::INFO)
163        .with_thread_ids(true)
164        .with_thread_names(true)
165        .with_level(true)
166        .with_target(true);
167
168    let subscriber = subscriber.finish();
169    let _ = tracing::subscriber::set_global_default(subscriber);
170}