std_logger/
config.rs

1//! Configuration of the logger.
2
3use std::env;
4use std::marker::PhantomData;
5
6use log::{kv, LevelFilter, SetLoggerError};
7
8use crate::format::{Format, Gcloud, Json, LogFmt};
9#[cfg(feature = "log-panic")]
10use crate::PANIC_TARGET;
11use crate::{Logger, Targets};
12
13/// Configuration of the logger.
14///
15/// It support three logging formats:
16///  * [`logfmt`](Config::logfmt) and
17///  * [`json`](Config::json) and
18///  * [`gcloud`](Config::gcloud).
19#[derive(Debug)]
20#[must_use = "the logger must be initialised using `init` or `try_init`"]
21pub struct Config<F, Kvs> {
22    filter: LevelFilter,
23    add_loc: Option<bool>,
24    targets: Targets,
25    kvs: Kvs,
26    format: PhantomData<F>,
27}
28
29impl Config<(), NoKvs> {
30    /// Logfmt following <https://www.brandur.org/logfmt>.
31    pub fn logfmt() -> Config<LogFmt, NoKvs> {
32        Config::new(NoKvs)
33    }
34
35    /// Structured logging using JSON.
36    pub fn json() -> Config<Json, NoKvs> {
37        Config::new(NoKvs)
38    }
39
40    /// Google Cloud Platform structured logging using JSON, following
41    /// <https://cloud.google.com/logging/docs/structured-logging>.
42    pub fn gcloud() -> Config<Gcloud, NoKvs> {
43        Config::new(NoKvs)
44    }
45}
46
47impl<F, Kvs> Config<F, Kvs>
48where
49    F: Format + Send + Sync + 'static,
50    Kvs: kv::Source + Send + Sync + 'static,
51{
52    fn new(kvs: Kvs) -> Config<F, Kvs> {
53        Config {
54            filter: get_max_level(),
55            add_loc: None,
56            targets: get_log_targets(),
57            kvs,
58            format: PhantomData,
59        }
60    }
61
62    /// Add the key-values `kvs` to all logged messages.
63    pub fn with_kvs<K>(self, kvs: K) -> Config<F, K>
64    where
65        K: kv::Source + Send + Sync + 'static,
66    {
67        Config {
68            filter: self.filter,
69            add_loc: self.add_loc,
70            targets: self.targets,
71            kvs,
72            format: self.format,
73        }
74    }
75
76    /// Enable or disable logging of the call location.
77    ///
78    /// Default to enable if the debug (or lower) messages are enabled.
79    pub fn with_call_location(self, enable: bool) -> Config<F, Kvs> {
80        Config {
81            filter: self.filter,
82            add_loc: Some(enable),
83            targets: self.targets,
84            kvs: self.kvs,
85            format: self.format,
86        }
87    }
88
89    /// Initialise the logger.
90    ///
91    /// See the [crate level documentation] for more.
92    ///
93    /// [crate level documentation]: index.html
94    ///
95    /// # Panics
96    ///
97    /// This will panic if the logger fails to initialise. Use [`Config::try_init`] if
98    /// you want to handle the error yourself.
99    pub fn init(self) {
100        self.try_init()
101            .unwrap_or_else(|err| panic!("failed to initialise the logger: {err}"));
102    }
103
104    /// Try to initialise the logger.
105    ///
106    /// Unlike [`Config::init`] this doesn't panic when the logger fails to initialise.
107    /// See the [crate level documentation] for more.
108    ///
109    /// [`init`]: fn.init.html
110    /// [crate level documentation]: index.html
111    pub fn try_init(self) -> Result<(), SetLoggerError> {
112        let logger = Box::new(Logger {
113            filter: self.filter,
114            add_loc: self.add_loc.unwrap_or(self.filter >= LevelFilter::Debug),
115            targets: self.targets,
116            kvs: self.kvs,
117            format: self.format,
118        });
119        log::set_boxed_logger(logger)?;
120        log::set_max_level(self.filter);
121
122        #[cfg(feature = "log-panic")]
123        std::panic::set_hook(Box::new(log_panic));
124        Ok(())
125    }
126}
127
128/// Get the maximum log level based on the environment.
129pub(crate) fn get_max_level() -> LevelFilter {
130    for var in &["LOG", "LOG_LEVEL"] {
131        if let Ok(level) = env::var(var) {
132            if let Ok(level) = level.parse() {
133                return level;
134            }
135        }
136    }
137
138    if env::var("TRACE").is_ok() {
139        LevelFilter::Trace
140    } else if env::var("DEBUG").is_ok() {
141        LevelFilter::Debug
142    } else {
143        LevelFilter::Info
144    }
145}
146
147/// Get the targets to log, if any.
148pub(crate) fn get_log_targets() -> Targets {
149    match env::var("LOG_TARGET") {
150        Ok(ref targets) if !targets.is_empty() => {
151            Targets::Only(targets.split(',').map(Into::into).collect())
152        }
153        _ => Targets::All,
154    }
155}
156
157/// Panic hook that logs the panic using [`log::error!`].
158#[cfg(feature = "log-panic")]
159#[allow(deprecated)] // Change to PanicHookInfo info after MSRV is updated to 1.82.
160fn log_panic(info: &std::panic::PanicInfo<'_>) {
161    use std::backtrace::Backtrace;
162    use std::thread;
163
164    let mut record = log::Record::builder();
165    let thread = thread::current();
166    let thread_name = thread.name().unwrap_or("unnamed");
167    let backtrace = Backtrace::force_capture();
168
169    let key_values = [
170        ("backtrace", kv::Value::from_display(&backtrace)),
171        ("thread_name", kv::Value::from(thread_name)),
172    ];
173    let key_values = key_values.as_slice();
174
175    let _ = record
176        .level(log::Level::Error)
177        .target(PANIC_TARGET)
178        .key_values(&key_values);
179
180    if let Some(location) = info.location() {
181        let _ = record
182            .file(Some(location.file()))
183            .line(Some(location.line()));
184    };
185
186    // Format for {info}: "panicked at '$message', $file:$line:$col".
187    log::logger().log(
188        &record
189            .args(format_args!("thread '{thread_name}' {info}"))
190            .build(),
191    );
192}
193
194/// No initial key-values.
195#[derive(Debug)]
196pub struct NoKvs;
197
198impl kv::Source for NoKvs {
199    fn visit<'kvs>(
200        &'kvs self,
201        _: &mut dyn log::kv::VisitSource<'kvs>,
202    ) -> Result<(), log::kv::Error> {
203        Ok(())
204    }
205
206    fn get<'v>(&'v self, _: log::kv::Key<'_>) -> Option<log::kv::Value<'v>> {
207        None
208    }
209
210    fn count(&self) -> usize {
211        0
212    }
213}