1use core::cell::{Cell, RefCell};
9use core::fmt;
10use std::sync::Arc;
11
12use crate::context;
13use crate::error::{Error, Result};
14use crate::filter::Filter;
15use crate::level::Level;
16use crate::record::{Field, Metadata, Record, same_output_key};
17use crate::sink::{Sink, trim_buffer};
18use crate::time;
19use crate::value::{OwnedField, Value};
20
21use crate::builder::LoggerBuilder;
22
23#[cfg_attr(all(feature = "logfmt"), doc = "```")]
34#[cfg_attr(not(all(feature = "logfmt")), doc = "```ignore")]
35pub type ErrorHandler = Arc<dyn Fn(&Error) + Send + Sync>;
43
44pub const REDACTED: &str = "[REDACTED]";
46
47#[cfg_attr(all(feature = "json"), doc = "```")]
90#[cfg_attr(not(all(feature = "json")), doc = "```ignore")]
91#[derive(Clone)]
103pub struct Logger {
104 inner: Arc<LoggerInner>,
105}
106
107impl std::panic::UnwindSafe for Logger {}
111impl std::panic::RefUnwindSafe for Logger {}
112
113pub(crate) struct LoggerInner {
114 pub(crate) filter: Filter,
115 pub(crate) sinks: Box<[Box<dyn Sink>]>,
116 pub(crate) defaults: Box<[OwnedField]>,
117 pub(crate) default_target: Box<str>,
118 pub(crate) capture_timestamps: bool,
119 pub(crate) capture_context: bool,
120 pub(crate) flush_level: Level,
121 pub(crate) on_error: Option<ErrorHandler>,
122 pub(crate) redact: Box<[Box<str>]>,
124}
125
126impl Drop for LoggerInner {
127 fn drop(&mut self) {
128 for sink in &self.sinks {
129 if let Err(e) = sink.shutdown() {
130 report_with(self.on_error.as_ref(), &e);
131 }
132 }
133 }
134}
135
136thread_local! {
137 static DISPATCH_DEPTH: Cell<u32> = const { Cell::new(0) };
141 static IN_HANDLER: Cell<bool> = const { Cell::new(false) };
143 static MESSAGE_BUF: RefCell<String> = const { RefCell::new(String::new()) };
145}
146
147pub(crate) fn in_dispatch() -> bool {
150 DISPATCH_DEPTH.try_with(Cell::get).unwrap_or(0) > 0
151}
152
153struct DepthGuard;
154
155impl DepthGuard {
156 fn enter() -> Self {
157 let _tls_may_be_gone = DISPATCH_DEPTH.try_with(|d| d.set(d.get().saturating_add(1)));
158 Self
159 }
160}
161
162impl Drop for DepthGuard {
163 fn drop(&mut self) {
164 let _tls_may_be_gone = DISPATCH_DEPTH.try_with(|d| d.set(d.get().saturating_sub(1)));
165 }
166}
167
168fn report_with(handler: Option<&ErrorHandler>, error: &Error) {
169 let Some(handler) = handler else {
170 return;
171 };
172 let _tls_may_be_gone = IN_HANDLER.try_with(|busy| {
173 if busy.get() {
174 return;
175 }
176 busy.set(true);
177 struct Reset<'a>(&'a Cell<bool>);
178 impl Drop for Reset<'_> {
179 fn drop(&mut self) {
180 self.0.set(false);
181 }
182 }
183 let _reset = Reset(busy);
184 handler(error);
185 });
186}
187
188impl Logger {
189 pub fn builder() -> LoggerBuilder {
191 LoggerBuilder::new()
192 }
193
194 pub(crate) fn from_inner(inner: LoggerInner) -> Self {
195 Self {
196 inner: Arc::new(inner),
197 }
198 }
199
200 #[must_use]
202 pub fn threshold_for(&self, target: &str) -> Level {
203 self.inner.filter.threshold_for(target)
204 }
205
206 #[inline]
210 #[must_use]
211 pub fn enabled(&self, target: &str, level: Level) -> bool {
212 self.inner.filter.is_enabled(target, level)
213 }
214
215 #[must_use]
218 pub fn min_level(&self) -> Level {
219 self.inner.filter.min_level()
220 }
221
222 #[must_use]
224 pub fn filter(&self) -> &Filter {
225 &self.inner.filter
226 }
227
228 #[must_use]
230 pub fn sink_count(&self) -> usize {
231 self.inner.sinks.len()
232 }
233
234 #[must_use]
237 pub fn default_target(&self) -> &str {
238 &self.inner.default_target
239 }
240
241 #[inline]
248 pub fn log(&self, level: Level, message: &str, fields: &[Field<'_>]) {
249 let _reported_to_handler = self.try_log(level, &self.inner.default_target, message, fields);
250 }
251
252 #[inline]
259 pub fn try_log(
260 &self,
261 level: Level,
262 target: &str,
263 message: &str,
264 fields: &[Field<'_>],
265 ) -> Result<()> {
266 self.try_emit(Metadata::new(level, target), message, fields)
267 }
268
269 #[inline]
278 pub fn try_emit(
279 &self,
280 metadata: Metadata<'_>,
281 message: &str,
282 fields: &[Field<'_>],
283 ) -> Result<()> {
284 if !self.enabled(metadata.target, metadata.level) {
285 return Ok(());
286 }
287 self.emit_unchecked(metadata, message, fields, &[])
288 }
289
290 pub fn flush(&self) -> Result<()> {
298 self.for_each_sink(|sink| sink.flush())
299 }
300
301 pub fn shutdown(&self) -> Result<()> {
309 self.for_each_sink(|sink| sink.shutdown())
310 }
311
312 pub fn flush_guard(&self) -> crate::FlushGuard {
316 crate::FlushGuard::new(self.clone())
317 }
318
319 fn for_each_sink(&self, op: impl Fn(&dyn Sink) -> Result<()>) -> Result<()> {
320 let _depth = DepthGuard::enter();
321 let mut first = None;
322 for sink in &self.inner.sinks {
323 if let Err(e) = op(&**sink) {
324 self.report(&e);
325 if first.is_none() {
326 first = Some(e);
327 }
328 }
329 }
330 first.map_or(Ok(()), Err)
331 }
332
333 pub(crate) fn report(&self, error: &Error) {
334 report_with(self.inner.on_error.as_ref(), error);
335 }
336
337 pub(crate) fn emit_fmt(
342 &self,
343 metadata: Metadata<'_>,
344 args: fmt::Arguments<'_>,
345 fields: &[Field<'_>],
346 extra: &[Field<'_>],
347 ) {
348 if let Some(message) = args.as_str() {
349 let _reported = self.emit_unchecked(metadata, message, fields, extra);
350 return;
351 }
352 let pooled = MESSAGE_BUF.try_with(|cell| {
353 let mut buf = cell.try_borrow_mut().ok()?;
354 self.emit_rendered(&mut buf, metadata, args, fields, extra);
355 trim_buffer(&mut buf);
356 Some(())
357 });
358 if !matches!(pooled, Ok(Some(()))) {
359 self.emit_rendered(&mut String::new(), metadata, args, fields, extra);
361 }
362 }
363
364 fn emit_rendered(
365 &self,
366 buf: &mut String,
367 metadata: Metadata<'_>,
368 args: fmt::Arguments<'_>,
369 fields: &[Field<'_>],
370 extra: &[Field<'_>],
371 ) {
372 buf.clear();
373 let rendered = fmt::write(buf, args);
374 let _reported = self.emit_unchecked(metadata, buf, fields, extra);
375 if rendered.is_err() {
376 self.report(&Error::Format(fmt::Error));
379 }
380 }
381
382 pub(crate) fn emit_unchecked(
386 &self,
387 mut metadata: Metadata<'_>,
388 message: &str,
389 fields: &[Field<'_>],
390 extra: &[Field<'_>],
391 ) -> Result<()> {
392 if self.inner.capture_timestamps && metadata.timestamp_unix_nanos.is_none() {
393 metadata.timestamp_unix_nanos = Some(time::now_unix_nanos());
394 }
395 if self.inner.capture_context {
396 context::with_snapshot(|ctx| self.assemble(metadata, message, fields, ctx, extra))
397 } else {
398 self.assemble(metadata, message, fields, &[], extra)
399 }
400 }
401
402 fn assemble(
403 &self,
404 metadata: Metadata<'_>,
405 message: &str,
406 fields: &[Field<'_>],
407 ctx: &[Field<'_>],
408 extra: &[Field<'_>],
409 ) -> Result<()> {
410 let unique;
413 let fields = if has_duplicate_keys(fields) {
414 unique = last_occurrences(fields);
415 unique.as_slice()
416 } else {
417 fields
418 };
419 let redacting = !self.inner.redact.is_empty();
420 if self.inner.defaults.is_empty()
421 && extra.is_empty()
422 && !redacting
423 && (ctx.is_empty() || !any_key_in(ctx, fields))
424 {
425 return self.dispatch(&Record::with_context(metadata, message, fields, ctx));
426 }
427
428 let defaults = &self.inner.defaults;
432 if ctx.is_empty() && extra.is_empty() && !redacting && defaults.len() <= INLINE_FIELDS {
433 let mut buf = [EMPTY_FIELD; INLINE_FIELDS];
434 let mut len = 0;
435 for owned in defaults {
436 if !fields.iter().any(|f| same_output_key(f.key, &owned.key)) {
437 buf[len] = owned.as_field();
438 len += 1;
439 }
440 }
441 return self.dispatch(&Record::with_context(
442 metadata,
443 message,
444 fields,
445 &buf[..len],
446 ));
447 }
448
449 if redacting && fields.iter().any(|f| self.is_redacted(f.key)) {
450 let mut call = FieldBuf::new();
451 for f in fields {
452 call.push(self.redacted(*f));
453 }
454 self.merge_ambient(metadata, message, call.as_slice(), ctx, extra)
455 } else {
456 self.merge_ambient(metadata, message, fields, ctx, extra)
457 }
458 }
459
460 fn merge_ambient(
463 &self,
464 metadata: Metadata<'_>,
465 message: &str,
466 fields: &[Field<'_>],
467 ctx: &[Field<'_>],
468 extra: &[Field<'_>],
469 ) -> Result<()> {
470 let mut ambient = FieldBuf::new();
473 for layer in [extra, ctx] {
474 for f in layer.iter().rev() {
475 self.push_ambient(&mut ambient, *f, fields);
476 }
477 }
478 for owned in self.inner.defaults.iter().rev() {
479 self.push_ambient(&mut ambient, owned.as_field(), fields);
480 }
481 ambient.reverse();
482 self.dispatch(&Record::with_context(
483 metadata,
484 message,
485 fields,
486 ambient.as_slice(),
487 ))
488 }
489
490 #[inline]
491 fn push_ambient<'a>(&self, ambient: &mut FieldBuf<'a>, field: Field<'a>, call: &[Field<'_>]) {
492 let shadowed = call.iter().any(|c| same_output_key(c.key, field.key))
493 || ambient
494 .as_slice()
495 .iter()
496 .any(|a| same_output_key(a.key, field.key));
497 if !shadowed {
498 ambient.push(self.redacted(field));
499 }
500 }
501
502 #[inline]
503 fn redacted<'a>(&self, field: Field<'a>) -> Field<'a> {
504 if self.is_redacted(field.key) {
505 Field {
506 key: field.key,
507 value: Value::Str(REDACTED),
508 }
509 } else {
510 field
511 }
512 }
513
514 fn is_redacted(&self, key: &str) -> bool {
517 if self.inner.redact.is_empty() {
518 return false;
519 }
520 let last = key.rsplit('.').next().unwrap_or(key);
521 self.inner
522 .redact
523 .iter()
524 .any(|r| redact_key_matches(r, key) || redact_key_matches(r, last))
525 }
526
527 fn dispatch(&self, record: &Record<'_>) -> Result<()> {
528 let _depth = DepthGuard::enter();
529 let mut first = None;
530 for sink in &self.inner.sinks {
531 if let Err(e) = sink.write_record(record) {
532 self.report(&e);
533 if first.is_none() {
534 first = Some(e);
535 }
536 }
537 }
538 if record.metadata.level >= self.inner.flush_level {
539 for sink in &self.inner.sinks {
540 if let Err(e) = sink.request_flush() {
541 self.report(&e);
542 if first.is_none() {
543 first = Some(e);
544 }
545 }
546 }
547 }
548 first.map_or(Ok(()), Err)
549 }
550}
551
552pub(crate) fn normalize_redact_key(key: &str) -> String {
554 key.chars()
555 .map(|c| match c {
556 '-' => '_',
557 c => c.to_ascii_lowercase(),
558 })
559 .collect()
560}
561
562#[inline]
565fn redact_key_matches(normalized: &str, key: &str) -> bool {
566 normalized.len() == key.len()
567 && normalized.bytes().zip(key.bytes()).all(|(r, k)| {
568 let k = if k == b'-' {
569 b'_'
570 } else {
571 k.to_ascii_lowercase()
572 };
573 r == k
574 })
575}
576
577fn any_key_in(a: &[Field<'_>], b: &[Field<'_>]) -> bool {
578 !b.is_empty()
579 && a.iter()
580 .any(|x| b.iter().any(|y| same_output_key(x.key, y.key)))
581}
582
583#[inline]
590fn has_duplicate_keys(fields: &[Field<'_>]) -> bool {
591 if fields.len() < 2 {
592 return false;
593 }
594 let mut seen = 0_u64;
595 for (i, field) in fields.iter().enumerate() {
596 let class = key_class(field.key);
597 let bit = 1_u64 << class;
598 if seen & bit != 0
599 && fields[..i].iter().any(|earlier| {
600 key_class(earlier.key) == class && same_output_key(earlier.key, field.key)
601 })
602 {
603 return true;
604 }
605 seen |= bit;
606 }
607 false
608}
609
610#[inline]
614fn key_class(key: &str) -> u32 {
615 let tail = match key.as_bytes() {
616 [.., a, b] => u16::from_le_bytes([*a, *b]),
617 [b] => u16::from(*b),
618 [] => 0,
619 };
620 let hash = u64::from(tail).wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 58;
621 #[allow(clippy::cast_possible_truncation)] let class = hash as u32;
623 class
624}
625
626#[cold]
628fn last_occurrences<'a>(fields: &[Field<'a>]) -> FieldBuf<'a> {
629 let mut unique = FieldBuf::new();
630 for (i, f) in fields.iter().enumerate() {
631 if !fields[i + 1..]
632 .iter()
633 .any(|later| same_output_key(later.key, f.key))
634 {
635 unique.push(*f);
636 }
637 }
638 unique
639}
640
641impl fmt::Debug for Logger {
642 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643 f.debug_struct("Logger")
644 .field("filter", &self.inner.filter)
645 .field("sinks", &self.inner.sinks.len())
646 .field("default_fields", &self.inner.defaults.len())
647 .field("default_target", &self.inner.default_target)
648 .field("capture_timestamps", &self.inner.capture_timestamps)
649 .field("capture_context", &self.inner.capture_context)
650 .field("flush_level", &self.inner.flush_level)
651 .field("redacted_keys", &self.inner.redact.len())
652 .finish_non_exhaustive()
653 }
654}
655
656const INLINE_FIELDS: usize = 16;
658
659const EMPTY_FIELD: Field<'static> = Field {
660 key: "",
661 value: Value::Null,
662};
663
664struct FieldBuf<'a> {
667 stack: [Field<'a>; INLINE_FIELDS],
668 len: usize,
669 heap: Vec<Field<'a>>,
670}
671
672impl<'a> FieldBuf<'a> {
673 fn new() -> Self {
674 Self {
675 stack: [EMPTY_FIELD; INLINE_FIELDS],
676 len: 0,
677 heap: Vec::new(),
678 }
679 }
680
681 #[inline]
682 fn push(&mut self, field: Field<'a>) {
683 if !self.heap.is_empty() {
684 self.heap.push(field);
685 } else if let Some(slot) = self.stack.get_mut(self.len) {
686 *slot = field;
687 self.len += 1;
688 } else {
689 self.heap.reserve(self.len * 2);
690 self.heap.extend_from_slice(&self.stack[..self.len]);
691 self.heap.push(field);
692 }
693 }
694
695 #[inline]
696 fn as_slice(&self) -> &[Field<'a>] {
697 if self.heap.is_empty() {
698 &self.stack[..self.len]
699 } else {
700 &self.heap
701 }
702 }
703
704 fn reverse(&mut self) {
705 if self.heap.is_empty() {
706 self.stack[..self.len].reverse();
707 } else {
708 self.heap.reverse();
709 }
710 }
711}
712
713#[cfg(all(test, feature = "logfmt", feature = "json"))]
714mod tests {
715 use super::*;
716 use crate::format::{JsonFormat, LogfmtFormat};
717 use std::io::Write;
718 use std::sync::Mutex;
719 use std::sync::atomic::{AtomicUsize, Ordering};
720
721 #[derive(Default, Clone)]
722 struct MemBuf(Arc<Mutex<Vec<u8>>>);
723
724 impl Write for MemBuf {
725 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
726 self.0.lock().unwrap().extend_from_slice(buf);
727 Ok(buf.len())
728 }
729 fn flush(&mut self) -> std::io::Result<()> {
730 Ok(())
731 }
732 }
733
734 impl MemBuf {
735 fn text(&self) -> String {
736 String::from_utf8(self.0.lock().unwrap().clone()).unwrap()
737 }
738 }
739
740 fn quiet() -> LoggerBuilder {
741 Logger::builder()
742 .level(Level::Trace)
743 .no_timestamps()
744 .no_context()
745 }
746
747 struct Failing(AtomicUsize);
748 impl Sink for Failing {
749 fn write_record(&self, _record: &Record<'_>) -> Result<()> {
750 let _n = self.0.fetch_add(1, Ordering::Relaxed);
751 Err(Error::Configuration("boom"))
752 }
753 fn flush(&self) -> Result<()> {
754 Err(Error::Configuration("flush boom"))
755 }
756 }
757
758 #[test]
759 fn test_logger_enabled_and_min_level() {
760 let logger = Logger::builder()
761 .level(Level::Warn)
762 .target_level("loud", Level::Trace)
763 .with_sink(crate::sink::NullSink::new())
764 .build();
765 assert!(logger.enabled("loud", Level::Trace));
766 assert!(!logger.enabled("other", Level::Info));
767 assert_eq!(logger.min_level(), Level::Trace);
768 assert_eq!(logger.threshold_for("loud::x"), Level::Trace);
769 assert_eq!(logger.filter().default_level(), Level::Warn);
770 assert_eq!(logger.sink_count(), 1);
771 }
772
773 #[test]
774 fn test_logger_log_writes_through_logfmt() {
775 let buf = MemBuf::default();
776 let logger = quiet().writer(buf.clone(), LogfmtFormat::new()).build();
777 logger.log(Level::Info, "hi", &[Field::new("k", 1_u64)]);
778 assert_eq!(buf.text(), "level=info target=\"\" message=hi k=1\n");
779 }
780
781 #[test]
782 fn test_logger_default_target_is_used_by_log() {
783 let buf = MemBuf::default();
785 let logger = Logger::builder()
786 .filter_directive("myapp=debug")
787 .default_target("myapp")
788 .no_timestamps()
789 .writer(buf.clone(), JsonFormat::new())
790 .build();
791 assert_eq!(logger.default_target(), "myapp");
792 logger.log(Level::Error, "kept", &[]);
793 assert!(
794 buf.text().contains("\"target\":\"myapp\""),
795 "{}",
796 buf.text()
797 );
798 }
799
800 #[test]
801 fn test_logger_context_is_spliced_in() {
802 let buf = MemBuf::default();
803 let logger = Logger::builder()
804 .level(Level::Trace)
805 .no_timestamps()
806 .writer(buf.clone(), LogfmtFormat::new())
807 .build();
808 context::clear();
809 let guard = context::with_trace_id("tx-42");
810 logger.log(Level::Info, "hello", &[]);
811 drop(guard);
812 assert!(buf.text().contains("trace_id=tx-42"), "{}", buf.text());
813 }
814
815 #[test]
816 fn test_logger_duplicate_keys_follow_precedence() {
817 context::clear();
819 let buf = MemBuf::default();
820 let logger = Logger::builder()
821 .level(Level::Trace)
822 .no_timestamps()
823 .with_default_field("user", "default")
824 .with_default_field("service", "api")
825 .with_default_field("region", "eu")
826 .writer(buf.clone(), JsonFormat::new())
827 .build();
828 let _a = context::with_field("user", "outer");
829 let _b = context::with_field("user", "inner");
830 let _c = context::with_field("region", "us");
831 logger.log(Level::Error, "m", &[Field::new("region", "call")]);
832 let out = buf.text();
833 assert_eq!(
834 out,
835 "{\"level\":\"error\",\"target\":\"\",\"message\":\"m\",\"service\":\"api\",\
836 \"user\":\"inner\",\"region\":\"call\"}\n"
837 );
838 context::clear();
839 }
840
841 #[test]
842 fn test_logger_context_vs_call_dedup_without_defaults() {
843 context::clear();
844 let buf = MemBuf::default();
845 let logger = Logger::builder()
846 .level(Level::Trace)
847 .no_timestamps()
848 .writer(buf.clone(), LogfmtFormat::new())
849 .build();
850 let _t = context::with_field("tenant", "ctx");
851 logger.log(Level::Info, "m", &[Field::new("tenant", "call")]);
852 assert_eq!(buf.text(), "level=info target=\"\" message=m tenant=call\n");
853 context::clear();
854 }
855
856 #[test]
857 fn test_logger_extra_context_shadows_thread_context() {
858 context::clear();
859 let buf = MemBuf::default();
860 let logger = Logger::builder()
861 .level(Level::Trace)
862 .no_timestamps()
863 .writer(buf.clone(), LogfmtFormat::new())
864 .build();
865 let _t = context::with_field("span", "thread");
866 let extra = [Field::new("span", "extra"), Field::new("depth", 2_u8)];
867 logger.emit_fmt(
868 Metadata::new(Level::Info, "t"),
869 format_args!("m"),
870 &[],
871 &extra,
872 );
873 assert_eq!(
874 buf.text(),
875 "level=info target=t message=m span=extra depth=2\n"
876 );
877 context::clear();
878 }
879
880 #[test]
881 fn test_logger_redacts_keys_everywhere() {
882 context::clear();
883 let buf = MemBuf::default();
884 let logger = Logger::builder()
885 .level(Level::Trace)
886 .no_timestamps()
887 .redact_common_secrets()
888 .redact_key("Session_ID")
889 .with_default_field("api_key", "k-123")
890 .writer(buf.clone(), LogfmtFormat::new())
891 .build();
892 let _c = context::with_field("authorization", "Bearer x");
893 logger.log(
894 Level::Info,
895 "login",
896 &[
897 Field::new("user", "alice"),
898 Field::new("db.PASSWORD", "hunter2"),
899 Field::new("session_id", "s1"),
900 ],
901 );
902 let out = buf.text();
903 for secret in ["k-123", "Bearer", "hunter2", "s1"] {
904 assert!(!out.contains(secret), "{secret} leaked: {out}");
905 }
906 assert!(out.contains("user=alice"), "{out}");
907 assert_eq!(out.matches(REDACTED).count(), 4, "{out}");
908 context::clear();
909 }
910
911 #[test]
912 fn test_logger_redaction_never_evaluates_lazy_values() {
913 struct Explodes;
914 impl fmt::Display for Explodes {
915 fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
916 Err(fmt::Error)
917 }
918 }
919 let buf = MemBuf::default();
920 let logger = quiet()
921 .redact_key("token")
922 .writer(buf.clone(), JsonFormat::new())
923 .build();
924 let result = logger.try_log(
925 Level::Info,
926 "t",
927 "m",
928 &[Field::new("token", Value::Display(&Explodes))],
929 );
930 assert!(result.is_ok());
931 assert!(buf.text().contains("\"token\":\"[REDACTED]\""));
932 }
933
934 #[test]
935 fn test_logger_more_than_sixteen_ambient_fields_spill_correctly() {
936 context::clear();
937 let buf = MemBuf::default();
938 let mut builder = Logger::builder().level(Level::Trace).no_timestamps();
939 let keys: Vec<String> = (0..20).map(|i| format!("d{i}")).collect();
940 for (i, k) in keys.iter().enumerate() {
941 builder = builder.with_default_field(k.clone(), i as u32);
942 }
943 let logger = builder.writer(buf.clone(), JsonFormat::new()).build();
944 let _c = context::with_field("c", 1_u8);
945 logger.log(Level::Info, "ok", &[Field::new("d0", "call")]);
946 let out = buf.text();
947 assert!(out.contains("\"d0\":\"call\""), "{out}");
948 assert_eq!(out.matches("\"d0\"").count(), 1, "{out}");
949 for (i, k) in keys.iter().enumerate().skip(1) {
950 assert!(out.contains(&format!("\"{k}\":{i}")), "missing {k}: {out}");
951 }
952 assert!(out.contains("\"c\":1"));
953 context::clear();
954 }
955
956 #[test]
957 fn test_logger_dispatch_continues_after_sink_error_and_reports_each() {
958 let failing = Arc::new(Failing(AtomicUsize::new(0)));
960 let calls = Arc::new(AtomicUsize::new(0));
961 let calls2 = Arc::clone(&calls);
962 let buf = MemBuf::default();
963 let logger = quiet()
964 .flush_on(Level::Off)
965 .with_sink(Arc::clone(&failing))
966 .with_sink(Failing(AtomicUsize::new(0)))
967 .writer(buf.clone(), JsonFormat::new())
968 .on_error(Arc::new(move |_| {
969 let _n = calls2.fetch_add(1, Ordering::Relaxed);
970 }))
971 .build();
972 let result = logger.try_log(Level::Info, "t", "m", &[]);
973 assert!(matches!(result, Err(Error::Configuration("boom"))));
974 assert!(buf.text().contains("\"message\":\"m\""));
975 assert_eq!(failing.0.load(Ordering::Relaxed), 1);
976 assert_eq!(
977 calls.load(Ordering::Relaxed),
978 2,
979 "one call per failing sink"
980 );
981 }
982
983 #[test]
984 fn test_logger_flush_on_level_flushes_and_reports() {
985 let calls = Arc::new(AtomicUsize::new(0));
986 let calls2 = Arc::clone(&calls);
987 let logger = quiet()
988 .flush_on(Level::Warn)
989 .with_sink(Failing(AtomicUsize::new(0)))
990 .on_error(Arc::new(move |_| {
991 let _n = calls2.fetch_add(1, Ordering::Relaxed);
992 }))
993 .build();
994 logger.log(Level::Info, "below", &[]);
995 assert_eq!(calls.load(Ordering::Relaxed), 1, "write error only");
996 logger.log(Level::Warn, "at", &[]);
997 assert_eq!(calls.load(Ordering::Relaxed), 3, "write and flush errors");
998 assert!(logger.flush().is_err());
999 assert!(logger.shutdown().is_err());
1000 }
1001
1002 #[test]
1003 fn test_logger_on_error_recursion_is_bounded() {
1004 use std::sync::OnceLock;
1007 static SELF: OnceLock<Logger> = OnceLock::new();
1008 static CALLS: AtomicUsize = AtomicUsize::new(0);
1009 let logger = quiet()
1010 .with_sink(Failing(AtomicUsize::new(0)))
1011 .on_error(Arc::new(|_| {
1012 let _n = CALLS.fetch_add(1, Ordering::Relaxed);
1013 if let Some(l) = SELF.get() {
1014 l.log(Level::Error, "sink failed", &[]);
1015 }
1016 }))
1017 .build();
1018 assert!(SELF.set(logger).is_ok());
1019 if let Some(l) = SELF.get() {
1020 l.log(Level::Error, "first", &[]);
1021 }
1022 assert_eq!(CALLS.load(Ordering::Relaxed), 2);
1025 }
1026
1027 #[test]
1028 fn test_logger_drop_shuts_sinks_down() {
1029 struct CountShutdown(Arc<AtomicUsize>);
1030 impl Sink for CountShutdown {
1031 fn write_record(&self, _: &Record<'_>) -> Result<()> {
1032 Ok(())
1033 }
1034 fn flush(&self) -> Result<()> {
1035 Ok(())
1036 }
1037 fn shutdown(&self) -> Result<()> {
1038 let _n = self.0.fetch_add(1, Ordering::Relaxed);
1039 Ok(())
1040 }
1041 }
1042 let count = Arc::new(AtomicUsize::new(0));
1043 let logger = quiet().with_sink(CountShutdown(Arc::clone(&count))).build();
1044 let clone = logger.clone();
1045 drop(logger);
1046 assert_eq!(count.load(Ordering::Relaxed), 0, "a clone is still alive");
1047 drop(clone);
1048 assert_eq!(count.load(Ordering::Relaxed), 1);
1049 }
1050
1051 #[test]
1052 fn test_logger_emit_fmt_renders_arguments_and_reports_format_errors() {
1053 struct Broken;
1054 impl fmt::Display for Broken {
1055 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1056 f.write_str("partial")?;
1057 Err(fmt::Error)
1058 }
1059 }
1060 let calls = Arc::new(AtomicUsize::new(0));
1061 let calls2 = Arc::clone(&calls);
1062 let buf = MemBuf::default();
1063 let logger = quiet()
1064 .writer(buf.clone(), LogfmtFormat::new())
1065 .on_error(Arc::new(move |e| {
1066 assert!(matches!(e, Error::Format(_)));
1067 let _n = calls2.fetch_add(1, Ordering::Relaxed);
1068 }))
1069 .build();
1070 let n = 7;
1071 logger.emit_fmt(
1072 Metadata::new(Level::Info, "t"),
1073 format_args!("n={n}"),
1074 &[],
1075 &[],
1076 );
1077 logger.emit_fmt(
1078 Metadata::new(Level::Info, "t"),
1079 format_args!("{}", Broken),
1080 &[],
1081 &[],
1082 );
1083 let out = buf.text();
1084 assert!(out.contains("message=\"n=7\""), "{out}");
1085 assert!(out.contains("message=partial"), "{out}");
1086 assert_eq!(calls.load(Ordering::Relaxed), 1);
1087 }
1088
1089 #[test]
1090 fn test_logger_timestamps_are_added_when_enabled() {
1091 let buf = MemBuf::default();
1092 let logger = Logger::builder()
1093 .no_context()
1094 .writer(buf.clone(), LogfmtFormat::new())
1095 .build();
1096 logger.log(Level::Info, "t", &[]);
1097 assert!(buf.text().starts_with("timestamp="), "{}", buf.text());
1098 let explicit = Metadata::new(Level::Info, "x").with_timestamp(5);
1099 logger.try_emit(explicit, "m", &[]).unwrap();
1100 assert!(buf.text().contains("timestamp=5 "), "{}", buf.text());
1101 }
1102
1103 #[test]
1104 fn test_logger_debug_lists_configuration() {
1105 let logger = quiet().with_sink(crate::sink::NullSink::new()).build();
1106 let text = format!("{logger:?}");
1107 assert!(text.contains("flush_level: Error"), "{text}");
1108 assert!(text.contains("sinks: 1"), "{text}");
1109 }
1110
1111 #[test]
1112 fn test_logger_is_unwind_safe_and_thread_safe() {
1113 fn assert_traits<T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
1114 assert_traits::<Logger>();
1115 }
1116
1117 #[test]
1118 fn test_field_buf_spills_and_reverses() {
1119 let mut buf = FieldBuf::new();
1120 for i in 0..20_u64 {
1121 buf.push(Field::new("k", i));
1122 }
1123 assert_eq!(buf.as_slice().len(), 20);
1124 buf.reverse();
1125 assert_eq!(buf.as_slice()[0].value, Value::U64(19));
1126 let mut small = FieldBuf::new();
1127 small.push(Field::new("a", 1_u8));
1128 small.push(Field::new("b", 2_u8));
1129 small.reverse();
1130 assert_eq!(small.as_slice()[0].key, "b");
1131 }
1132}