Skip to main content

log_io/compat/
log_bridge.rs

1//! `log::Log` implementation.
2
3use core::fmt;
4
5use log::kv::{self, Key, VisitSource, VisitValue};
6
7use crate::level::Level;
8use crate::logger::Logger;
9use crate::record::{Field, Metadata};
10use crate::value::Value;
11
12/// Routes records from the [`log`] facade into a [`Logger`].
13///
14/// * Levels map one to one (`log::Level::Warn` becomes
15///   [`Level::Warn`], and so on).
16/// * The record's target, file and line are preserved.
17/// * The message's format arguments are rendered into a reused
18///   thread-local buffer (no allocation in steady state).
19/// * Structured key-values (`log::info!(user = "alice"; "login")`)
20///   become fields. Strings, integers, floats, booleans and chars keep
21///   their type; anything else is rendered through its `Display`
22///   implementation when a sink serializes the record. Up to 16
23///   key-values are collected without allocating.
24///
25/// # Examples
26///
27#[cfg_attr(all(feature = "logfmt", feature = "log"), doc = "```")]
28#[cfg_attr(not(all(feature = "logfmt", feature = "log")), doc = "```ignore")]
29/// use log_io::compat::LogBridge;
30/// use log_io::{Level, Logger};
31///
32/// let logger = Logger::builder().level(Level::Info).stdout_logfmt().build();
33/// LogBridge::new(logger).install()?;
34///
35/// log::info!(user = "alice", attempts = 3; "login succeeded");
36/// # Ok::<(), log::SetLoggerError>(())
37/// ```
38#[derive(Debug, Clone)]
39pub struct LogBridge {
40    logger: Logger,
41}
42
43impl LogBridge {
44    /// Wrap `logger`.
45    #[must_use]
46    pub const fn new(logger: Logger) -> Self {
47        Self { logger }
48    }
49
50    /// The wrapped logger.
51    #[must_use]
52    pub const fn logger(&self) -> &Logger {
53        &self.logger
54    }
55
56    /// Install as the `log` crate's global logger and set
57    /// `log::max_level` from the logger's [`Logger::min_level`], so
58    /// disabled `log` macros cost a single comparison.
59    ///
60    /// # Errors
61    ///
62    /// Returns [`log::SetLoggerError`] if a `log` logger is already
63    /// installed.
64    pub fn install(self) -> Result<(), log::SetLoggerError> {
65        let max = to_level_filter(self.logger.min_level());
66        log::set_boxed_logger(Box::new(self))?;
67        log::set_max_level(max);
68        Ok(())
69    }
70}
71
72/// Convert a log-io level threshold to a [`log::LevelFilter`].
73///
74/// # Examples
75///
76#[cfg_attr(all(feature = "log"), doc = "```")]
77#[cfg_attr(not(all(feature = "log")), doc = "```ignore")]
78/// use log_io::compat::to_level_filter;
79/// use log_io::Level;
80///
81/// assert_eq!(to_level_filter(Level::Warn), log::LevelFilter::Warn);
82/// assert_eq!(to_level_filter(Level::Off), log::LevelFilter::Off);
83/// ```
84#[must_use]
85pub const fn to_level_filter(level: Level) -> log::LevelFilter {
86    match level {
87        Level::Trace => log::LevelFilter::Trace,
88        Level::Debug => log::LevelFilter::Debug,
89        Level::Info => log::LevelFilter::Info,
90        Level::Warn => log::LevelFilter::Warn,
91        Level::Error => log::LevelFilter::Error,
92        Level::Off => log::LevelFilter::Off,
93    }
94}
95
96const fn from_log_level(level: log::Level) -> Level {
97    match level {
98        log::Level::Error => Level::Error,
99        log::Level::Warn => Level::Warn,
100        log::Level::Info => Level::Info,
101        log::Level::Debug => Level::Debug,
102        log::Level::Trace => Level::Trace,
103    }
104}
105
106impl log::Log for LogBridge {
107    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
108        self.logger
109            .enabled(metadata.target(), from_log_level(metadata.level()))
110    }
111
112    fn log(&self, record: &log::Record<'_>) {
113        let level = from_log_level(record.level());
114        let target = record.target();
115        if !self.logger.enabled(target, level) {
116            return;
117        }
118        let mut metadata = Metadata::new(level, target);
119        if let (Some(file), Some(line)) = (record.file(), record.line()) {
120            metadata = metadata.with_location(file, line);
121        }
122
123        let mut pairs = Pairs::default();
124        // `visit` only fails if a visitor does; ours never does.
125        let _infallible = record.key_values().visit(&mut pairs);
126        if pairs.heap.is_empty() {
127            let mut fields = [EMPTY; INLINE];
128            for (slot, (key, value)) in fields.iter_mut().zip(pairs.inline.iter().flatten()) {
129                *slot = Field {
130                    key: key.as_str(),
131                    value: convert(value),
132                };
133            }
134            self.logger
135                .emit_fmt(metadata, *record.args(), &fields[..pairs.len], &[]);
136        } else {
137            let fields: Vec<Field<'_>> = pairs
138                .heap
139                .iter()
140                .map(|(key, value)| Field {
141                    key: key.as_str(),
142                    value: convert(value),
143                })
144                .collect();
145            self.logger.emit_fmt(metadata, *record.args(), &fields, &[]);
146        }
147    }
148
149    fn flush(&self) {
150        // Failures were already passed to the logger's error handler.
151        let _reported = self.logger.flush();
152    }
153}
154
155const INLINE: usize = 16;
156
157const EMPTY: Field<'static> = Field {
158    key: "",
159    value: Value::Null,
160};
161
162/// Key-value pairs of one record: inline up to [`INLINE`], then all on
163/// the heap.
164struct Pairs<'kvs> {
165    inline: [Option<(Key<'kvs>, kv::Value<'kvs>)>; INLINE],
166    len: usize,
167    heap: Vec<(Key<'kvs>, kv::Value<'kvs>)>,
168}
169
170impl Default for Pairs<'_> {
171    fn default() -> Self {
172        Self {
173            inline: [const { None }; INLINE],
174            len: 0,
175            heap: Vec::new(),
176        }
177    }
178}
179
180impl<'kvs> VisitSource<'kvs> for Pairs<'kvs> {
181    fn visit_pair(&mut self, key: Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
182        if !self.heap.is_empty() {
183            self.heap.push((key, value));
184        } else if let Some(slot) = self.inline.get_mut(self.len) {
185            *slot = Some((key, value));
186            self.len += 1;
187        } else {
188            self.heap.reserve(INLINE * 2);
189            self.heap
190                .extend(self.inline.iter_mut().filter_map(Option::take));
191            self.heap.push((key, value));
192        }
193        Ok(())
194    }
195}
196
197/// The primitive inside a `log::kv::Value`, if it has one that can be
198/// borrowed for the value's whole lifetime.
199enum Primitive<'v> {
200    Str(&'v str),
201    I64(i64),
202    U64(u64),
203    I128(i128),
204    U128(u128),
205    F64(f64),
206    Bool(bool),
207    Char(char),
208    Null,
209    Other,
210}
211
212struct Probe<'v>(Primitive<'v>);
213
214impl<'v> VisitValue<'v> for Probe<'v> {
215    fn visit_any(&mut self, _value: kv::Value<'_>) -> Result<(), kv::Error> {
216        self.0 = Primitive::Other;
217        Ok(())
218    }
219    fn visit_null(&mut self) -> Result<(), kv::Error> {
220        self.0 = Primitive::Null;
221        Ok(())
222    }
223    fn visit_u64(&mut self, value: u64) -> Result<(), kv::Error> {
224        self.0 = Primitive::U64(value);
225        Ok(())
226    }
227    fn visit_i64(&mut self, value: i64) -> Result<(), kv::Error> {
228        self.0 = Primitive::I64(value);
229        Ok(())
230    }
231    fn visit_u128(&mut self, value: u128) -> Result<(), kv::Error> {
232        self.0 = Primitive::U128(value);
233        Ok(())
234    }
235    fn visit_i128(&mut self, value: i128) -> Result<(), kv::Error> {
236        self.0 = Primitive::I128(value);
237        Ok(())
238    }
239    fn visit_f64(&mut self, value: f64) -> Result<(), kv::Error> {
240        self.0 = Primitive::F64(value);
241        Ok(())
242    }
243    fn visit_bool(&mut self, value: bool) -> Result<(), kv::Error> {
244        self.0 = Primitive::Bool(value);
245        Ok(())
246    }
247    fn visit_str(&mut self, _value: &str) -> Result<(), kv::Error> {
248        // Not borrowable for 'v; fall back to Display.
249        self.0 = Primitive::Other;
250        Ok(())
251    }
252    fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), kv::Error> {
253        self.0 = Primitive::Str(value);
254        Ok(())
255    }
256    fn visit_char(&mut self, value: char) -> Result<(), kv::Error> {
257        self.0 = Primitive::Char(value);
258        Ok(())
259    }
260}
261
262fn convert<'a>(value: &'a kv::Value<'_>) -> Value<'a> {
263    let mut probe = Probe(Primitive::Other);
264    if value.visit(&mut probe).is_err() {
265        return Value::Display(value as &dyn fmt::Display);
266    }
267    match probe.0 {
268        Primitive::Str(s) => Value::Str(s),
269        Primitive::I64(n) => Value::I64(n),
270        Primitive::U64(n) => Value::U64(n),
271        Primitive::I128(n) => Value::I128(n),
272        Primitive::U128(n) => Value::U128(n),
273        Primitive::F64(n) => Value::F64(n),
274        Primitive::Bool(b) => Value::Bool(b),
275        Primitive::Char(c) => Value::Char(c),
276        Primitive::Null => Value::Null,
277        Primitive::Other => Value::Display(value as &dyn fmt::Display),
278    }
279}
280
281#[cfg(all(test, feature = "logfmt"))]
282mod tests {
283    use super::*;
284    use crate::format::LogfmtFormat;
285    use log::Log;
286    use std::io::Write;
287    use std::sync::{Arc, Mutex};
288
289    #[derive(Default, Clone)]
290    struct Capture(Arc<Mutex<Vec<u8>>>);
291    impl Write for Capture {
292        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
293            self.0.lock().unwrap().extend_from_slice(buf);
294            Ok(buf.len())
295        }
296        fn flush(&mut self) -> std::io::Result<()> {
297            Ok(())
298        }
299    }
300    impl Capture {
301        fn take(&self) -> String {
302            String::from_utf8(std::mem::take(&mut *self.0.lock().unwrap())).unwrap()
303        }
304    }
305
306    fn bridge(level: Level) -> (LogBridge, Capture) {
307        let out = Capture::default();
308        let logger = Logger::builder()
309            .level(level)
310            .no_timestamps()
311            .no_context()
312            .writer(out.clone(), LogfmtFormat::new())
313            .build();
314        (LogBridge::new(logger), out)
315    }
316
317    #[test]
318    fn test_log_bridge_maps_levels_targets_and_location() {
319        let (bridge, out) = bridge(Level::Trace);
320        let n = 3;
321        bridge.log(
322            &log::Record::builder()
323                .level(log::Level::Warn)
324                .target("app::db")
325                .file(Some("src/db.rs"))
326                .line(Some(9))
327                .args(format_args!("slow by {n}x"))
328                .build(),
329        );
330        assert_eq!(
331            out.take(),
332            "level=warn target=app::db message=\"slow by 3x\" file=src/db.rs line=9\n"
333        );
334        for (log_level, ours) in [
335            (log::Level::Error, "error"),
336            (log::Level::Info, "info"),
337            (log::Level::Debug, "debug"),
338            (log::Level::Trace, "trace"),
339        ] {
340            bridge.log(
341                &log::Record::builder()
342                    .level(log_level)
343                    .args(format_args!("x"))
344                    .build(),
345            );
346            assert!(out.take().starts_with(&format!("level={ours} ")));
347        }
348    }
349
350    #[test]
351    fn test_log_bridge_respects_filter() {
352        let (bridge, out) = bridge(Level::Warn);
353        let meta = log::Metadata::builder()
354            .level(log::Level::Info)
355            .target("x")
356            .build();
357        assert!(!bridge.enabled(&meta));
358        bridge.log(
359            &log::Record::builder()
360                .level(log::Level::Info)
361                .args(format_args!("no"))
362                .build(),
363        );
364        assert_eq!(out.take(), "");
365        let meta = log::Metadata::builder()
366            .level(log::Level::Error)
367            .target("x")
368            .build();
369        assert!(bridge.enabled(&meta));
370    }
371
372    #[test]
373    fn test_log_bridge_key_values_keep_types() {
374        let (bridge, out) = bridge(Level::Trace);
375        let addr = std::net::Ipv4Addr::LOCALHOST;
376        let kvs: &[(&str, kv::Value<'_>)] = &[
377            ("user", kv::Value::from("alice")),
378            ("n", kv::Value::from(-3_i64)),
379            ("u", kv::Value::from(7_u64)),
380            ("big", kv::Value::from(u128::MAX)),
381            ("neg", kv::Value::from(i128::MIN)),
382            ("ratio", kv::Value::from(0.5_f64)),
383            ("ok", kv::Value::from(true)),
384            ("c", kv::Value::from('z')),
385            ("addr", kv::Value::from_display(&addr)),
386            ("none", kv::Value::null()),
387        ];
388        bridge.log(
389            &log::Record::builder()
390                .level(log::Level::Info)
391                .target("t")
392                .key_values(&kvs)
393                .args(format_args!("m"))
394                .build(),
395        );
396        assert_eq!(
397            out.take(),
398            format!(
399                "level=info target=t message=m user=alice n=-3 u=7 big={} neg={} ratio=0.5 ok=true c=z addr=127.0.0.1 none=\n",
400                u128::MAX,
401                i128::MIN
402            )
403        );
404    }
405
406    #[test]
407    fn test_log_bridge_many_key_values_spill_to_heap() {
408        let (bridge, out) = bridge(Level::Trace);
409        let keys: Vec<String> = (0..20).map(|i| format!("k{i}")).collect();
410        let kvs: Vec<(&str, kv::Value<'_>)> = keys
411            .iter()
412            .enumerate()
413            .map(|(i, k)| (k.as_str(), kv::Value::from(i as u64)))
414            .collect();
415        bridge.log(
416            &log::Record::builder()
417                .level(log::Level::Info)
418                .key_values(&kvs)
419                .args(format_args!("many"))
420                .build(),
421        );
422        let text = out.take();
423        for (i, k) in keys.iter().enumerate() {
424            assert!(text.contains(&format!(" {k}={i}")), "{k}: {text}");
425        }
426    }
427
428    #[test]
429    fn test_log_bridge_flush_and_accessors() {
430        let (bridge, _out) = bridge(Level::Info);
431        bridge.flush();
432        assert_eq!(bridge.logger().min_level(), Level::Info);
433        assert_eq!(to_level_filter(Level::Trace), log::LevelFilter::Trace);
434        assert_eq!(to_level_filter(Level::Debug), log::LevelFilter::Debug);
435        assert_eq!(to_level_filter(Level::Info), log::LevelFilter::Info);
436        assert_eq!(to_level_filter(Level::Error), log::LevelFilter::Error);
437    }
438}