1use 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#[cfg_attr(all(feature = "logfmt", feature = "log"), doc = "```")]
28#[cfg_attr(not(all(feature = "logfmt", feature = "log")), doc = "```ignore")]
29#[derive(Debug, Clone)]
39pub struct LogBridge {
40 logger: Logger,
41}
42
43impl LogBridge {
44 #[must_use]
46 pub const fn new(logger: Logger) -> Self {
47 Self { logger }
48 }
49
50 #[must_use]
52 pub const fn logger(&self) -> &Logger {
53 &self.logger
54 }
55
56 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#[cfg_attr(all(feature = "log"), doc = "```")]
77#[cfg_attr(not(all(feature = "log")), doc = "```ignore")]
78#[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 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 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
162struct 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
197enum 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 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}