1use std::io::Write;
9use std::sync::Arc;
10
11use crate::context;
12use crate::error::Result;
13use crate::filter::Filter;
14use crate::format::Format;
15use crate::level::Level;
16use crate::record::{Field, Metadata, Record};
17use crate::sink::{Sink, StderrSink, StdoutSink, WriterSink};
18use crate::time;
19use crate::value::Value;
20
21#[derive(Debug, Clone)]
24enum OwnedValue {
25 Null,
26 Bool(bool),
27 I64(i64),
28 U64(u64),
29 F64(f64),
30 Str(String),
31 Char(char),
32}
33
34impl OwnedValue {
35 fn as_value(&self) -> Value<'_> {
36 match self {
37 Self::Null => Value::Null,
38 Self::Bool(b) => Value::Bool(*b),
39 Self::I64(n) => Value::I64(*n),
40 Self::U64(n) => Value::U64(*n),
41 Self::F64(n) => Value::F64(*n),
42 Self::Str(s) => Value::Str(s.as_str()),
43 Self::Char(c) => Value::Char(*c),
44 }
45 }
46
47 fn from_value(v: Value<'_>) -> Self {
48 match v {
49 Value::Null => Self::Null,
50 Value::Bool(b) => Self::Bool(b),
51 Value::I64(n) => Self::I64(n),
52 Value::U64(n) => Self::U64(n),
53 Value::F64(n) => Self::F64(n),
54 Value::Str(s) => Self::Str(s.to_owned()),
55 Value::Char(c) => Self::Char(c),
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
61struct OwnedField {
62 key: String,
63 value: OwnedValue,
64}
65
66pub type ErrorHandler = Arc<dyn Fn(&crate::error::Error) + Send + Sync>;
72
73#[derive(Clone)]
75pub struct Logger {
76 inner: Arc<LoggerInner>,
77}
78
79struct LoggerInner {
80 filter: Filter,
81 sinks: Box<[Arc<dyn Sink>]>,
82 default_fields: Box<[OwnedField]>,
83 capture_timestamps: bool,
84 capture_context: bool,
85 on_error: Option<ErrorHandler>,
86}
87
88impl Logger {
89 #[must_use]
91 pub fn builder() -> LoggerBuilder {
92 LoggerBuilder {
93 filter: Filter::new(Level::Info),
94 sinks: Vec::new(),
95 default_fields: Vec::new(),
96 capture_timestamps: true,
97 capture_context: true,
98 on_error: None,
99 }
100 }
101
102 #[must_use]
104 pub fn threshold_for(&self, target: &str) -> Level {
105 self.inner.filter.threshold_for(target)
106 }
107
108 #[must_use]
112 pub fn enabled(&self, target: &str, level: Level) -> bool {
113 self.inner.filter.is_enabled(target, level)
114 }
115
116 #[must_use]
119 pub fn min_level(&self) -> Level {
120 let mut min = self.inner.filter.default_level();
121 for rule in self.inner.filter.rules() {
122 if rule.level < min {
123 min = rule.level;
124 }
125 }
126 min
127 }
128
129 pub fn log(&self, level: Level, message: &str, fields: &[Field<'_>]) {
133 let _ = self.try_log(level, "", message, fields);
134 }
135
136 pub fn try_log(
147 &self,
148 level: Level,
149 target: &str,
150 message: &str,
151 fields: &[Field<'_>],
152 ) -> Result<()> {
153 let metadata = Metadata::new(level, target);
154 self.try_emit(metadata, message, fields)
155 }
156
157 pub fn try_emit(
170 &self,
171 mut metadata: Metadata<'_>,
172 message: &str,
173 fields: &[Field<'_>],
174 ) -> Result<()> {
175 if !self
176 .inner
177 .filter
178 .is_enabled(metadata.target, metadata.level)
179 {
180 return Ok(());
181 }
182 if self.inner.capture_timestamps && metadata.timestamp_unix_nanos.is_none() {
183 metadata = metadata.with_timestamp(time::now_unix_nanos());
184 }
185
186 let result = if self.inner.capture_context {
187 context::with_snapshot(|ctx_fields| {
188 self.dispatch_with_context(metadata, message, fields, ctx_fields)
189 })
190 } else {
191 self.dispatch_with_context(metadata, message, fields, &[])
192 };
193
194 if let Err(ref e) = result {
195 if let Some(handler) = &self.inner.on_error {
196 handler(e);
197 }
198 }
199 result
200 }
201
202 pub fn flush(&self) -> Result<()> {
209 let mut first_err: Option<crate::error::Error> = None;
210 for sink in self.inner.sinks.iter() {
211 if let Err(e) = sink.flush() {
212 if first_err.is_none() {
213 first_err = Some(e);
214 }
215 }
216 }
217 match first_err {
218 Some(e) => Err(e),
219 None => Ok(()),
220 }
221 }
222
223 #[must_use]
226 pub fn sink_count(&self) -> usize {
227 self.inner.sinks.len()
228 }
229
230 fn dispatch_with_context(
231 &self,
232 metadata: Metadata<'_>,
233 message: &str,
234 fields: &[Field<'_>],
235 ctx_fields: &[Field<'_>],
236 ) -> Result<()> {
237 if self.inner.default_fields.is_empty() {
244 let record = Record::with_context(metadata, message, fields, ctx_fields);
245 return self.dispatch(&record);
246 }
247
248 const STACK: usize = 16;
250 let total = self.inner.default_fields.len() + ctx_fields.len();
251 if total <= STACK {
252 let mut buf: [Field<'_>; STACK] = [Field {
253 key: "",
254 value: Value::Null,
255 }; STACK];
256 let mut n = 0;
257 for f in self.inner.default_fields.iter() {
258 buf[n] = Field::new(f.key.as_str(), f.value.as_value());
259 n += 1;
260 }
261 for f in ctx_fields {
262 buf[n] = *f;
263 n += 1;
264 }
265 let record = Record::with_context(metadata, message, fields, &buf[..n]);
266 self.dispatch(&record)
267 } else {
268 let mut merged: Vec<Field<'_>> = Vec::with_capacity(total);
269 for f in self.inner.default_fields.iter() {
270 merged.push(Field::new(f.key.as_str(), f.value.as_value()));
271 }
272 merged.extend_from_slice(ctx_fields);
273 let record = Record::with_context(metadata, message, fields, &merged);
274 self.dispatch(&record)
275 }
276 }
277
278 fn dispatch(&self, record: &Record<'_>) -> Result<()> {
279 let mut first_err: Option<crate::error::Error> = None;
280 for sink in self.inner.sinks.iter() {
281 if let Err(e) = sink.write_record(record) {
282 if first_err.is_none() {
283 first_err = Some(e);
284 }
285 }
286 }
287 match first_err {
288 Some(e) => Err(e),
289 None => Ok(()),
290 }
291 }
292}
293
294impl core::fmt::Debug for Logger {
295 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
296 f.debug_struct("Logger")
297 .field("filter", &self.inner.filter)
298 .field("sinks", &self.inner.sinks.len())
299 .field("default_fields", &self.inner.default_fields.len())
300 .field("capture_timestamps", &self.inner.capture_timestamps)
301 .field("capture_context", &self.inner.capture_context)
302 .finish()
303 }
304}
305
306pub struct LoggerBuilder {
313 filter: Filter,
314 sinks: Vec<Arc<dyn Sink>>,
315 default_fields: Vec<OwnedField>,
316 capture_timestamps: bool,
317 capture_context: bool,
318 on_error: Option<ErrorHandler>,
319}
320
321impl LoggerBuilder {
322 #[must_use]
324 pub fn level(mut self, level: Level) -> Self {
325 let prior_rules = self.filter.rules().to_vec();
326 self.filter = Filter::new(level);
327 for rule in prior_rules {
328 self.filter = self.filter.with_rule(rule.target, rule.level);
329 }
330 self
331 }
332
333 #[must_use]
335 pub fn filter(mut self, filter: Filter) -> Self {
336 self.filter = filter;
337 self
338 }
339
340 #[must_use]
342 pub fn target_level(mut self, target: impl Into<String>, level: Level) -> Self {
343 self.filter = self.filter.with_rule(target, level);
344 self
345 }
346
347 #[must_use]
351 pub fn filter_directive(mut self, directive: &str) -> Self {
352 if let Ok(f) = Filter::parse(directive) {
353 self.filter = f;
354 }
355 self
356 }
357
358 #[must_use]
361 pub fn no_timestamps(mut self) -> Self {
362 self.capture_timestamps = false;
363 self
364 }
365
366 #[must_use]
368 pub fn no_context(mut self) -> Self {
369 self.capture_context = false;
370 self
371 }
372
373 #[must_use]
376 pub fn with_default_field<'a, V: Into<Value<'a>>>(
377 mut self,
378 key: impl Into<String>,
379 value: V,
380 ) -> Self {
381 self.default_fields.push(OwnedField {
382 key: key.into(),
383 value: OwnedValue::from_value(value.into()),
384 });
385 self
386 }
387
388 #[must_use]
390 pub fn on_error(mut self, handler: ErrorHandler) -> Self {
391 self.on_error = Some(handler);
392 self
393 }
394
395 #[must_use]
399 pub fn with_sink(mut self, sink: impl Sink + 'static) -> Self {
400 self.sinks.push(Arc::new(sink));
401 self
402 }
403
404 #[must_use]
406 pub fn stdout<F>(mut self, format: F) -> Self
407 where
408 F: Format + Send + Sync + 'static,
409 {
410 self.sinks.push(Arc::new(StdoutSink::new(format)));
411 self
412 }
413
414 #[must_use]
416 pub fn stderr<F>(mut self, format: F) -> Self
417 where
418 F: Format + Send + Sync + 'static,
419 {
420 self.sinks.push(Arc::new(StderrSink::new(format)));
421 self
422 }
423
424 #[must_use]
426 pub fn writer<W, F>(mut self, writer: W, format: F) -> Self
427 where
428 W: Write + Send + 'static,
429 F: Format + Send + Sync + 'static,
430 {
431 self.sinks.push(Arc::new(WriterSink::new(writer, format)));
432 self
433 }
434
435 #[must_use]
444 pub fn build(self) -> Logger {
445 assert!(
446 !self.sinks.is_empty(),
447 "log_io: builder must install at least one sink before build()"
448 );
449 Logger {
450 inner: Arc::new(LoggerInner {
451 filter: self.filter,
452 sinks: self.sinks.into_boxed_slice(),
453 default_fields: self.default_fields.into_boxed_slice(),
454 capture_timestamps: self.capture_timestamps,
455 capture_context: self.capture_context,
456 on_error: self.on_error,
457 }),
458 }
459 }
460}
461
462#[cfg(feature = "json")]
466impl LoggerBuilder {
467 #[must_use]
469 pub fn stdout_json(self) -> Self {
470 self.stdout(crate::format::JsonFormat::new())
471 }
472
473 #[must_use]
475 pub fn stderr_json(self) -> Self {
476 self.stderr(crate::format::JsonFormat::new())
477 }
478}
479
480#[cfg(feature = "logfmt")]
481impl LoggerBuilder {
482 #[must_use]
484 pub fn stdout_logfmt(self) -> Self {
485 self.stdout(crate::format::LogfmtFormat::new())
486 }
487
488 #[must_use]
490 pub fn stderr_logfmt(self) -> Self {
491 self.stderr(crate::format::LogfmtFormat::new())
492 }
493}
494
495#[cfg(feature = "human")]
496impl LoggerBuilder {
497 #[must_use]
499 pub fn stdout_human(self) -> Self {
500 self.stdout(crate::format::HumanFormat::new())
501 }
502
503 #[must_use]
505 pub fn stderr_human(self) -> Self {
506 self.stderr(crate::format::HumanFormat::new())
507 }
508}
509
510#[cfg(all(test, feature = "std", feature = "logfmt", feature = "json"))]
511mod tests {
512 use super::*;
513 use std::io::Write;
514 use std::sync::{Arc, Mutex};
515
516 #[derive(Default, Clone)]
517 struct MemBuf(Arc<Mutex<Vec<u8>>>);
518
519 impl Write for MemBuf {
520 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
521 self.0.lock().unwrap().extend_from_slice(buf);
522 Ok(buf.len())
523 }
524 fn flush(&mut self) -> std::io::Result<()> {
525 Ok(())
526 }
527 }
528
529 fn logfmt_logger() -> (Logger, MemBuf) {
530 let buf = MemBuf::default();
531 let logger = Logger::builder()
532 .level(Level::Trace)
533 .no_timestamps()
534 .no_context()
535 .writer(buf.clone(), crate::format::LogfmtFormat::new())
536 .build();
537 (logger, buf)
538 }
539
540 #[test]
541 fn enabled_respects_filter() {
542 let (logger, _) = logfmt_logger();
543 assert!(logger.enabled("x", Level::Trace));
544 assert!(logger.enabled("x", Level::Error));
545 }
546
547 #[test]
548 fn min_level_is_lowest_configured() {
549 let logger = Logger::builder()
550 .level(Level::Warn)
551 .target_level("loud", Level::Trace)
552 .no_timestamps()
553 .no_context()
554 .with_sink(crate::sink::NullSink::new())
555 .build();
556 assert_eq!(logger.min_level(), Level::Trace);
557 }
558
559 #[test]
560 fn log_writes_through_logfmt() {
561 let (logger, buf) = logfmt_logger();
562 logger.log(Level::Info, "hi", &[Field::new("k", 1_u64)]);
563 let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap();
564 assert!(out.contains("level=info"));
565 assert!(out.contains("message=hi"));
566 assert!(out.contains("k=1"));
567 }
568
569 #[test]
570 fn filter_blocks_records_below_threshold() {
571 let buf = MemBuf::default();
572 let logger = Logger::builder()
573 .level(Level::Warn)
574 .no_timestamps()
575 .no_context()
576 .writer(buf.clone(), crate::format::LogfmtFormat::new())
577 .build();
578 logger.log(Level::Info, "skip", &[]);
579 logger.log(Level::Error, "keep", &[]);
580 let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap();
581 assert!(!out.contains("skip"));
582 assert!(out.contains("keep"));
583 }
584
585 #[test]
586 fn context_is_spliced_in() {
587 let buf = MemBuf::default();
588 let logger = Logger::builder()
589 .level(Level::Trace)
590 .no_timestamps()
591 .writer(buf.clone(), crate::format::LogfmtFormat::new())
592 .build();
593 crate::context::clear();
594 let guard = crate::context::with_trace_id("tx-42");
595 logger.log(Level::Info, "hello", &[]);
596 drop(guard);
597 let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap();
598 assert!(out.contains("trace_id=tx-42"), "{out}");
599 }
600
601 #[test]
602 fn default_fields_appear_on_every_record() {
603 let buf = MemBuf::default();
604 let logger = Logger::builder()
605 .level(Level::Trace)
606 .no_timestamps()
607 .no_context()
608 .with_default_field("service", "api")
609 .with_default_field("region", "us-east-1")
610 .writer(buf.clone(), crate::format::LogfmtFormat::new())
611 .build();
612 logger.log(Level::Info, "x", &[]);
613 logger.log(Level::Warn, "y", &[Field::new("port", 80_u32)]);
614 let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap();
615 assert_eq!(out.matches("service=api").count(), 2);
616 assert_eq!(out.matches("region=us-east-1").count(), 2);
617 assert!(out.contains("port=80"));
618 }
619
620 #[test]
621 fn dispatch_continues_after_sink_error() {
622 use std::sync::atomic::{AtomicUsize, Ordering};
623 struct FailingSink(AtomicUsize);
624 impl Sink for FailingSink {
625 fn write_record(&self, _record: &Record<'_>) -> Result<()> {
626 self.0.fetch_add(1, Ordering::Relaxed);
627 Err(crate::error::Error::Configuration("boom"))
628 }
629 fn flush(&self) -> Result<()> {
630 Ok(())
631 }
632 }
633 let failing: Arc<FailingSink> = Arc::new(FailingSink(AtomicUsize::new(0)));
634 let buf = MemBuf::default();
635 let logger = Logger::builder()
636 .level(Level::Trace)
637 .no_timestamps()
638 .no_context()
639 .with_sink(failing.clone())
640 .writer(buf.clone(), crate::format::JsonFormat::new())
641 .build();
642 let _ = logger.try_log(Level::Info, "t", "m", &[]);
643 let out = String::from_utf8(buf.0.lock().unwrap().clone()).unwrap();
645 assert!(out.contains("\"message\":\"m\""), "{out}");
646 assert_eq!(failing.0.load(Ordering::Relaxed), 1);
647 }
648
649 #[test]
650 fn on_error_callback_fires() {
651 use std::sync::atomic::{AtomicUsize, Ordering};
652 struct Bad;
653 impl Sink for Bad {
654 fn write_record(&self, _r: &Record<'_>) -> Result<()> {
655 Err(crate::error::Error::Configuration("nope"))
656 }
657 fn flush(&self) -> Result<()> {
658 Ok(())
659 }
660 }
661 let count = Arc::new(AtomicUsize::new(0));
662 let count_clone = count.clone();
663 let logger = Logger::builder()
664 .level(Level::Trace)
665 .no_timestamps()
666 .no_context()
667 .with_sink(Bad)
668 .on_error(Arc::new(move |_| {
669 count_clone.fetch_add(1, Ordering::Relaxed);
670 }))
671 .build();
672 let _ = logger.try_log(Level::Info, "t", "m", &[]);
673 assert_eq!(count.load(Ordering::Relaxed), 1);
674 }
675}