Skip to main content

charmed_log/
lib.rs

1#![forbid(unsafe_code)]
2// Allow pedantic lints for early-stage API ergonomics.
3#![allow(clippy::nursery)]
4#![allow(clippy::pedantic)]
5
6//! # Charmed Log
7//!
8//! A structured logging library designed for terminal applications.
9//!
10//! Charmed Log provides beautiful, structured logging output with support for:
11//! - Multiple log levels (trace, debug, info, warn, error, fatal)
12//! - Structured key-value pairs
13//! - Multiple output formatters (text, JSON, logfmt)
14//! - Integration with lipgloss for styled output
15//!
16//! ## Role in `charmed_rust`
17//!
18//! Charmed Log is the logging spine for TUI applications in this repo:
19//! - **wish** uses it for SSH session logging and diagnostics.
20//! - **demo_showcase** uses it for traceable, styled logs in tests and demos.
21//! - **lipgloss** supplies the styling used in human-readable formatters.
22//!
23//! ## Example
24//!
25//! ```rust
26//! use charmed_log::{Logger, Level};
27//!
28//! let logger = Logger::new();
29//! logger.info("Application started", &[("version", "1.0.0")]);
30//! ```
31//!
32//! ## Formatters
33//!
34//! - **Text**: Human-readable colored output (default)
35//! - **JSON**: Machine-readable JSON output
36//! - **Logfmt**: Key=value format for log aggregation
37
38use backtrace::Backtrace;
39use lipgloss::{Color, Style};
40use std::collections::HashMap;
41use std::fmt;
42use std::io::{self, Write};
43use std::sync::{Arc, RwLock};
44use std::time::{SystemTime, UNIX_EPOCH};
45use thiserror::Error;
46
47/// Log level for filtering messages.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[repr(i32)]
50pub enum Level {
51    /// Debug level (most verbose).
52    Debug = -4,
53    /// Info level (default).
54    Info = 0,
55    /// Warning level.
56    Warn = 4,
57    /// Error level.
58    Error = 8,
59    /// Fatal level (least verbose).
60    Fatal = 12,
61}
62
63impl Level {
64    /// Returns the string representation of the level.
65    #[must_use]
66    pub fn as_str(&self) -> &'static str {
67        match self {
68            Self::Debug => "debug",
69            Self::Info => "info",
70            Self::Warn => "warn",
71            Self::Error => "error",
72            Self::Fatal => "fatal",
73        }
74    }
75
76    /// Returns the uppercase string representation of the level.
77    #[must_use]
78    pub fn as_upper_str(&self) -> &'static str {
79        match self {
80            Self::Debug => "DEBU",
81            Self::Info => "INFO",
82            Self::Warn => "WARN",
83            Self::Error => "ERRO",
84            Self::Fatal => "FATA",
85        }
86    }
87}
88
89impl PartialOrd for Level {
90    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
91        Some(self.cmp(other))
92    }
93}
94
95impl Ord for Level {
96    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
97        (*self as i32).cmp(&(*other as i32))
98    }
99}
100
101impl fmt::Display for Level {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.as_str())
104    }
105}
106
107impl std::str::FromStr for Level {
108    type Err = ParseLevelError;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        match s.to_lowercase().as_str() {
112            "debug" => Ok(Self::Debug),
113            "info" => Ok(Self::Info),
114            "warn" => Ok(Self::Warn),
115            "error" => Ok(Self::Error),
116            "fatal" => Ok(Self::Fatal),
117            _ => Err(ParseLevelError(s.to_string())),
118        }
119    }
120}
121
122/// Error returned when parsing an invalid log level string.
123///
124/// This error occurs when calling [`Level::from_str`] with a string
125/// that doesn't match any known log level.
126///
127/// # Valid Level Strings
128///
129/// The following strings are accepted (case-insensitive):
130/// - `"debug"`
131/// - `"info"`
132/// - `"warn"`
133/// - `"error"`
134/// - `"fatal"`
135///
136/// # Example
137///
138/// ```rust
139/// use charmed_log::Level;
140/// use std::str::FromStr;
141///
142/// assert!(Level::from_str("info").is_ok());
143/// assert!(Level::from_str("INFO").is_ok());
144/// assert!(Level::from_str("invalid").is_err());
145/// ```
146#[derive(Error, Debug, Clone)]
147#[error("invalid level: {0:?}")]
148pub struct ParseLevelError(String);
149
150/// A specialized [`Result`] type for level parsing operations.
151pub type ParseResult<T> = std::result::Result<T, ParseLevelError>;
152
153/// Output formatter type.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
155pub enum Formatter {
156    /// Human-readable text format (default).
157    #[default]
158    Text,
159    /// JSON format.
160    Json,
161    /// Logfmt key=value format.
162    Logfmt,
163}
164
165/// Standard keys used in log records.
166pub mod keys {
167    /// Key for timestamp.
168    pub const TIMESTAMP: &str = "time";
169    /// Key for message.
170    pub const MESSAGE: &str = "msg";
171    /// Key for level.
172    pub const LEVEL: &str = "level";
173    /// Key for caller location.
174    pub const CALLER: &str = "caller";
175    /// Key for prefix.
176    pub const PREFIX: &str = "prefix";
177}
178
179/// Default time format.
180pub const DEFAULT_TIME_FORMAT: &str = "%Y/%m/%d %H:%M:%S";
181
182/// Styles for the text formatter.
183#[derive(Debug, Clone)]
184pub struct Styles {
185    /// Style for timestamps.
186    pub timestamp: Style,
187    /// Style for caller location.
188    pub caller: Style,
189    /// Style for prefix.
190    pub prefix: Style,
191    /// Style for messages.
192    pub message: Style,
193    /// Style for keys.
194    pub key: Style,
195    /// Style for values.
196    pub value: Style,
197    /// Style for separators.
198    pub separator: Style,
199    /// Styles for each level.
200    pub levels: HashMap<Level, Style>,
201    /// Custom styles for specific keys.
202    pub keys: HashMap<String, Style>,
203    /// Custom styles for specific values.
204    pub values: HashMap<String, Style>,
205}
206
207impl Default for Styles {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213impl Styles {
214    /// Creates a new Styles with default values.
215    #[must_use]
216    pub fn new() -> Self {
217        let mut levels = HashMap::new();
218        levels.insert(
219            Level::Debug,
220            Style::new().bold().foreground_color(Color::from("63")),
221        );
222        levels.insert(
223            Level::Info,
224            Style::new().bold().foreground_color(Color::from("86")),
225        );
226        levels.insert(
227            Level::Warn,
228            Style::new().bold().foreground_color(Color::from("192")),
229        );
230        levels.insert(
231            Level::Error,
232            Style::new().bold().foreground_color(Color::from("204")),
233        );
234        levels.insert(
235            Level::Fatal,
236            Style::new().bold().foreground_color(Color::from("134")),
237        );
238
239        Self {
240            timestamp: Style::new(),
241            caller: Style::new().faint(),
242            prefix: Style::new().bold().faint(),
243            message: Style::new(),
244            key: Style::new().faint(),
245            value: Style::new(),
246            separator: Style::new().faint(),
247            levels,
248            keys: HashMap::new(),
249            values: HashMap::new(),
250        }
251    }
252}
253
254/// Type alias for time function.
255pub type TimeFunction = fn(std::time::SystemTime) -> std::time::SystemTime;
256
257/// Returns the time in UTC.
258#[must_use]
259pub fn now_utc(t: SystemTime) -> SystemTime {
260    t // SystemTime is already timezone-agnostic
261}
262
263/// Type alias for caller formatter.
264pub type CallerFormatter = fn(&str, u32, &str) -> String;
265
266/// Type alias for error handler callback.
267///
268/// The error handler is called when an I/O error occurs during log writing.
269/// This allows applications to respond to logging failures (e.g., disk full,
270/// pipe closed, permission denied) instead of silently losing log messages.
271///
272/// # Example
273///
274/// ```rust
275/// use charmed_log::Logger;
276///
277/// let logger = Logger::new().with_error_handler(|err| {
278///     // Alert monitoring system, attempt fallback, etc.
279///     eprintln!("charmed_log: write failed: {}", err);
280/// });
281/// ```
282pub type ErrorHandler = Arc<dyn Fn(io::Error) + Send + Sync>;
283
284/// Short caller formatter - returns last 2 path segments and line.
285#[must_use]
286pub fn short_caller_formatter(file: &str, line: u32, _fn_name: &str) -> String {
287    let trimmed = trim_caller_path(file, 2);
288    format!("{trimmed}:{line}")
289}
290
291/// Long caller formatter - returns full path and line.
292#[must_use]
293pub fn long_caller_formatter(file: &str, line: u32, _fn_name: &str) -> String {
294    format!("{file}:{line}")
295}
296
297/// Trims a path to the last n segments.
298fn trim_caller_path(path: &str, n: usize) -> &str {
299    if n == 0 {
300        return path;
301    }
302
303    let mut last_idx = path.len();
304    for _ in 0..n {
305        if let Some(idx) = path[..last_idx].rfind('/') {
306            last_idx = idx;
307        } else {
308            return path;
309        }
310    }
311
312    &path[last_idx + 1..]
313}
314
315/// Caller information extracted from the call stack.
316#[derive(Debug, Clone)]
317pub struct CallerInfo {
318    /// Source file path.
319    pub file: String,
320    /// Line number.
321    pub line: u32,
322    /// Function name.
323    pub function: String,
324}
325
326impl CallerInfo {
327    /// Extracts caller information from the current call stack.
328    ///
329    /// The `skip` parameter indicates how many frames to skip from the
330    /// logging infrastructure to find the actual caller.
331    ///
332    /// # Performance Warning
333    ///
334    /// This method captures a full stack backtrace and performs symbol
335    /// resolution, which is a very expensive operation (~100μs or more).
336    /// Avoid calling this in hot paths or production code.
337    ///
338    /// Typical overhead: **100-1000x** slower than a normal log call.
339    #[must_use]
340    pub fn capture(skip: usize) -> Option<Self> {
341        let bt = Backtrace::new();
342        let frames: Vec<_> = bt.frames().iter().collect();
343
344        // Skip frames from backtrace crate + our own logging infrastructure
345        // Typical stack: backtrace::capture -> CallerInfo::capture -> log -> debug/info/etc -> user code
346        let skip_total = skip + 4;
347
348        for frame in frames.iter().skip(skip_total) {
349            for symbol in frame.symbols() {
350                // Get function name and filter out internal frames
351                let fn_name = symbol
352                    .name()
353                    .map(|n| n.to_string())
354                    .unwrap_or_else(|| "<unknown>".to_string());
355
356                // Skip frames from logging crate itself
357                if fn_name.contains("charmed_log::") || fn_name.contains("backtrace::") {
358                    continue;
359                }
360
361                let file = symbol
362                    .filename()
363                    .and_then(|p| p.to_str())
364                    .unwrap_or("<unknown>")
365                    .to_string();
366
367                let line = symbol.lineno().unwrap_or(0);
368
369                return Some(Self {
370                    file,
371                    line,
372                    function: fn_name,
373                });
374            }
375        }
376
377        None
378    }
379}
380
381/// Logger options.
382#[derive(Clone)]
383pub struct Options {
384    /// Time function for the logger.
385    pub time_function: TimeFunction,
386    /// Time format string.
387    pub time_format: String,
388    /// Minimum log level.
389    pub level: Level,
390    /// Log prefix.
391    pub prefix: String,
392    /// Whether to report timestamps.
393    pub report_timestamp: bool,
394    /// Whether to report caller location.
395    ///
396    /// # Performance Warning
397    ///
398    /// When enabled, captures a full stack backtrace on **every** log call,
399    /// which is approximately **100-1000x slower** than normal logging.
400    /// Only enable during active debugging sessions. Do NOT enable in production.
401    pub report_caller: bool,
402    /// Caller formatter function.
403    pub caller_formatter: CallerFormatter,
404    /// Caller offset for stack trace.
405    pub caller_offset: usize,
406    /// Default fields to include in all logs.
407    pub fields: Vec<(String, String)>,
408    /// Output formatter.
409    pub formatter: Formatter,
410}
411
412impl Default for Options {
413    fn default() -> Self {
414        Self {
415            time_function: now_utc,
416            time_format: DEFAULT_TIME_FORMAT.to_string(),
417            level: Level::Info,
418            prefix: String::new(),
419            report_timestamp: false,
420            report_caller: false,
421            caller_formatter: short_caller_formatter,
422            caller_offset: 0,
423            fields: Vec::new(),
424            formatter: Formatter::Text,
425        }
426    }
427}
428
429/// Internal logger state.
430struct LoggerInner {
431    writer: Box<dyn Write + Send + Sync>,
432    level: Level,
433    prefix: String,
434    time_function: TimeFunction,
435    time_format: String,
436    caller_offset: usize,
437    caller_formatter: CallerFormatter,
438    formatter: Formatter,
439    report_timestamp: bool,
440    report_caller: bool,
441    fields: Vec<(String, String)>,
442    styles: Styles,
443    /// Optional error handler for I/O failures during logging.
444    error_handler: Option<ErrorHandler>,
445    /// Whether we've already warned about I/O failures (to prevent infinite loops).
446    has_warned_io_failure: bool,
447    /// Whether we've already warned about caller reporting overhead.
448    warned_caller_overhead: bool,
449    /// Whether to suppress the caller overhead warning.
450    suppress_caller_warning: bool,
451}
452
453/// A structured logger instance.
454pub struct Logger {
455    inner: Arc<RwLock<LoggerInner>>,
456}
457
458impl Default for Logger {
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464impl Clone for Logger {
465    fn clone(&self) -> Self {
466        Self {
467            inner: Arc::clone(&self.inner),
468        }
469    }
470}
471
472impl fmt::Debug for Logger {
473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474        let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
475        f.debug_struct("Logger")
476            .field("level", &inner.level)
477            .field("prefix", &inner.prefix)
478            .field("formatter", &inner.formatter)
479            .field("report_timestamp", &inner.report_timestamp)
480            .field("report_caller", &inner.report_caller)
481            .finish()
482    }
483}
484
485impl Logger {
486    /// Creates a new logger with default settings.
487    #[must_use]
488    pub fn new() -> Self {
489        Self::with_options(Options::default())
490    }
491
492    /// Creates a new logger with the given options.
493    #[must_use]
494    pub fn with_options(opts: Options) -> Self {
495        Self {
496            inner: Arc::new(RwLock::new(LoggerInner {
497                writer: Box::new(io::stderr()),
498                level: opts.level,
499                prefix: opts.prefix,
500                time_function: opts.time_function,
501                time_format: opts.time_format,
502                caller_offset: opts.caller_offset,
503                caller_formatter: opts.caller_formatter,
504                formatter: opts.formatter,
505                report_timestamp: opts.report_timestamp,
506                report_caller: opts.report_caller,
507                fields: opts.fields,
508                styles: Styles::new(),
509                error_handler: None,
510                has_warned_io_failure: false,
511                warned_caller_overhead: false,
512                suppress_caller_warning: false,
513            })),
514        }
515    }
516
517    /// Sets the minimum log level.
518    pub fn set_level(&self, level: Level) {
519        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
520        inner.level = level;
521    }
522
523    /// Returns the current log level.
524    #[must_use]
525    pub fn level(&self) -> Level {
526        let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
527        inner.level
528    }
529
530    /// Sets the log prefix.
531    pub fn set_prefix(&self, prefix: impl Into<String>) {
532        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
533        inner.prefix = prefix.into();
534    }
535
536    /// Returns the current prefix.
537    #[must_use]
538    pub fn prefix(&self) -> String {
539        let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
540        inner.prefix.clone()
541    }
542
543    /// Sets whether to report timestamps.
544    pub fn set_report_timestamp(&self, report: bool) {
545        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
546        inner.report_timestamp = report;
547    }
548
549    /// Sets whether to report caller location.
550    ///
551    /// # Performance Warning
552    ///
553    /// When enabled, this captures a full stack backtrace on **every** log call,
554    /// which is approximately **100-1000x slower** than normal logging.
555    ///
556    /// | Configuration        | Typical Latency | Use Case       |
557    /// |---------------------|-----------------|----------------|
558    /// | Default (no caller) | ~100 ns         | Production     |
559    /// | With caller         | ~100 μs         | Debug only     |
560    ///
561    /// **Only enable during active debugging sessions.**
562    /// **Do NOT enable in production.**
563    ///
564    /// A runtime warning will be emitted on the first log call with caller
565    /// reporting enabled (unless suppressed via [`suppress_caller_warning`]).
566    ///
567    /// [`suppress_caller_warning`]: Logger::suppress_caller_warning
568    pub fn set_report_caller(&self, report: bool) {
569        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
570        inner.report_caller = report;
571    }
572
573    /// Suppresses the runtime performance warning for caller reporting.
574    ///
575    /// By default, when caller reporting is enabled, a warning is emitted to
576    /// stderr on the first log call to alert developers about the significant
577    /// performance overhead (~100-1000x slower).
578    ///
579    /// Call this method to suppress the warning when you have intentionally
580    /// enabled caller reporting and understand the performance implications.
581    ///
582    /// # Example
583    ///
584    /// ```rust
585    /// use charmed_log::Logger;
586    ///
587    /// let logger = Logger::new();
588    /// logger.set_report_caller(true);
589    /// logger.suppress_caller_warning();
590    /// // No warning will be emitted on first log call
591    /// logger.info("debug message", &[]);
592    /// ```
593    pub fn suppress_caller_warning(&self) {
594        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
595        inner.suppress_caller_warning = true;
596    }
597
598    /// Sets the time format.
599    pub fn set_time_format(&self, format: impl Into<String>) {
600        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
601        inner.time_format = format.into();
602    }
603
604    /// Sets the formatter.
605    pub fn set_formatter(&self, formatter: Formatter) {
606        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
607        inner.formatter = formatter;
608    }
609
610    /// Sets the styles.
611    pub fn set_styles(&self, styles: Styles) {
612        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
613        inner.styles = styles;
614    }
615
616    /// Creates a new logger with additional fields.
617    ///
618    /// This is the idiomatic Rust method name. For Go API compatibility,
619    /// use [`with`](Logger::with) instead.
620    #[must_use]
621    pub fn with_fields(&self, fields: &[(&str, &str)]) -> Self {
622        let inner = self.inner.read().unwrap_or_else(|e| e.into_inner());
623        let mut new_fields = inner.fields.clone();
624        new_fields.extend(fields.iter().map(|(k, v)| (k.to_string(), v.to_string())));
625
626        Self {
627            inner: Arc::new(RwLock::new(LoggerInner {
628                writer: Box::new(io::stderr()),
629                level: inner.level,
630                prefix: inner.prefix.clone(),
631                time_function: inner.time_function,
632                time_format: inner.time_format.clone(),
633                caller_offset: inner.caller_offset,
634                caller_formatter: inner.caller_formatter,
635                formatter: inner.formatter,
636                report_timestamp: inner.report_timestamp,
637                report_caller: inner.report_caller,
638                fields: new_fields,
639                styles: inner.styles.clone(),
640                error_handler: inner.error_handler.clone(),
641                has_warned_io_failure: false, // Reset warning state for new logger
642                warned_caller_overhead: false, // Reset for new logger
643                suppress_caller_warning: inner.suppress_caller_warning, // Inherit suppression
644            })),
645        }
646    }
647
648    /// Creates a new logger with additional fields (Go API compatibility).
649    ///
650    /// This method matches the Go `log.With()` API. It is equivalent to
651    /// [`with_fields`](Logger::with_fields).
652    ///
653    /// # Example
654    ///
655    /// ```rust
656    /// use charmed_log::Logger;
657    ///
658    /// let logger = Logger::new();
659    /// let ctx_logger = logger.with(&[("request_id", "abc123"), ("user", "alice")]);
660    /// ctx_logger.info("Processing request", &[]);
661    /// ```
662    #[must_use]
663    pub fn with(&self, fields: &[(&str, &str)]) -> Self {
664        self.with_fields(fields)
665    }
666
667    /// Creates a new logger with a different prefix.
668    #[must_use]
669    pub fn with_prefix(&self, prefix: impl Into<String>) -> Self {
670        let new_logger = self.with_fields(&[]);
671        new_logger.set_prefix(prefix);
672        new_logger
673    }
674
675    /// Sets an error handler for I/O failures during logging.
676    ///
677    /// When writing log output fails (e.g., disk full, pipe closed, permission
678    /// denied), the handler is called with the I/O error. This allows applications
679    /// to respond appropriately instead of silently losing log messages.
680    ///
681    /// # Default Behavior
682    ///
683    /// If no error handler is configured:
684    /// - First failure: A warning is printed to stderr (if available)
685    /// - Subsequent failures: Silent (to avoid infinite loops)
686    ///
687    /// # Example
688    ///
689    /// ```rust
690    /// use charmed_log::Logger;
691    /// use std::sync::atomic::{AtomicUsize, Ordering};
692    /// use std::sync::Arc;
693    ///
694    /// let error_count = Arc::new(AtomicUsize::new(0));
695    /// let counter = error_count.clone();
696    ///
697    /// let logger = Logger::new().with_error_handler(move |err| {
698    ///     counter.fetch_add(1, Ordering::Relaxed);
699    ///     eprintln!("Log write failed: {}", err);
700    /// });
701    /// ```
702    #[must_use]
703    pub fn with_error_handler<F>(self, handler: F) -> Self
704    where
705        F: Fn(io::Error) + Send + Sync + 'static,
706    {
707        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
708        inner.error_handler = Some(Arc::new(handler));
709        drop(inner);
710        self
711    }
712
713    /// Logs a message at the specified level.
714    ///
715    /// This method uses a single write lock for the entire operation (format + write)
716    /// to ensure configuration consistency. The only exception is caller info capture,
717    /// which happens inside formatting but is atomic with respect to the log entry.
718    pub fn log(&self, level: Level, msg: &str, keyvals: &[(&str, &str)]) {
719        // Use a single write lock for the entire operation to avoid race conditions
720        // between configuration reads and writes. This eliminates the window where
721        // another thread could modify settings between formatting and writing.
722        let mut inner = self.inner.write().unwrap_or_else(|e| e.into_inner());
723
724        // Check if we need to emit caller overhead warning
725        if inner.report_caller && !inner.warned_caller_overhead && !inner.suppress_caller_warning {
726            inner.warned_caller_overhead = true;
727            // Emit warning to stderr (separate from the log output)
728            let _ = io::stderr().write_all(
729                b"[charmed_log] PERF WARNING: caller reporting enabled - expect 100-1000x slowdown\n",
730            );
731        }
732
733        // Check level - early return while holding the lock is fine
734        if level < inner.level {
735            return;
736        }
737
738        // Format output using current configuration (atomic snapshot)
739        let mut output = String::new();
740        match inner.formatter {
741            Formatter::Text => Self::format_text_inner(&inner, level, msg, keyvals, &mut output),
742            Formatter::Json => Self::format_json_inner(&inner, level, msg, keyvals, &mut output),
743            Formatter::Logfmt => {
744                Self::format_logfmt_inner(&inner, level, msg, keyvals, &mut output);
745            }
746        }
747
748        // Write output with error handling (still holding the lock for consistency)
749        if let Err(e) = inner.writer.write_all(output.as_bytes()) {
750            // Call user-provided error handler if available
751            if let Some(ref handler) = inner.error_handler {
752                // Clone the handler to avoid holding the lock while calling it
753                let handler = Arc::clone(handler);
754                drop(inner);
755                handler(e);
756            } else if !inner.has_warned_io_failure {
757                // Default behavior: warn once to stderr, then go silent
758                inner.has_warned_io_failure = true;
759                drop(inner);
760                // Attempt to write warning to stderr (may also fail, but we try)
761                let _ =
762                    io::stderr().write_all(format!("charmed_log: write failed: {e}\n").as_bytes());
763            }
764        }
765    }
766
767    // =========================================================================
768    // Associated functions for formatting (no &self, used by log() for atomicity)
769    // =========================================================================
770
771    /// Format text output without requiring &self (for use in atomic log operation).
772    fn format_text_inner(
773        inner: &LoggerInner,
774        level: Level,
775        msg: &str,
776        keyvals: &[(&str, &str)],
777        output: &mut String,
778    ) {
779        let styles = &inner.styles;
780        let mut first = true;
781
782        // Timestamp
783        if inner.report_timestamp {
784            let ts = (inner.time_function)(SystemTime::now());
785            if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
786                let secs = duration.as_secs();
787                let ts_str = format_timestamp(secs, &inner.time_format);
788                let styled = styles.timestamp.render(&ts_str);
789                if !first {
790                    output.push(' ');
791                }
792                output.push_str(&styled);
793                first = false;
794            }
795        }
796
797        // Level
798        if let Some(level_style) = styles.levels.get(&level) {
799            let lvl = level_style.render(level.as_upper_str());
800            if !first {
801                output.push(' ');
802            }
803            output.push_str(&lvl);
804            first = false;
805        }
806
807        // Caller - extract actual caller info from backtrace
808        if inner.report_caller {
809            let caller_str = if let Some(info) = CallerInfo::capture(inner.caller_offset) {
810                (inner.caller_formatter)(&info.file, info.line, &info.function)
811            } else {
812                (inner.caller_formatter)("unknown", 0, "unknown")
813            };
814            let styled = styles.caller.render(&format!("<{caller_str}>"));
815            if !first {
816                output.push(' ');
817            }
818            output.push_str(&styled);
819            first = false;
820        }
821
822        // Prefix
823        if !inner.prefix.is_empty() {
824            let styled = styles.prefix.render(&format!("{}:", inner.prefix));
825            if !first {
826                output.push(' ');
827            }
828            output.push_str(&styled);
829            first = false;
830        }
831
832        // Message
833        if !msg.is_empty() {
834            let styled = styles.message.render(msg);
835            if !first {
836                output.push(' ');
837            }
838            output.push_str(&styled);
839            first = false;
840        }
841
842        // Default fields
843        for (key, value) in &inner.fields {
844            Self::format_text_keyval_inner(styles, key, value, &mut first, output);
845        }
846
847        // Additional keyvals
848        for (key, value) in keyvals {
849            Self::format_text_keyval_inner(styles, key, value, &mut first, output);
850        }
851
852        output.push('\n');
853    }
854
855    /// Format a key-value pair for text output.
856    fn format_text_keyval_inner(
857        styles: &Styles,
858        key: &str,
859        value: &str,
860        first: &mut bool,
861        output: &mut String,
862    ) {
863        let sep = styles.separator.render("=");
864        let key_styled = if let Some(style) = styles.keys.get(key) {
865            style.render(key)
866        } else {
867            styles.key.render(key)
868        };
869        let value_styled = if let Some(style) = styles.values.get(key) {
870            style.render(value)
871        } else {
872            styles.value.render(value)
873        };
874
875        if !*first {
876            output.push(' ');
877        }
878        output.push_str(&key_styled);
879        output.push_str(&sep);
880        output.push_str(&value_styled);
881        *first = false;
882    }
883
884    /// Format JSON output without requiring &self.
885    fn format_json_inner(
886        inner: &LoggerInner,
887        level: Level,
888        msg: &str,
889        keyvals: &[(&str, &str)],
890        output: &mut String,
891    ) {
892        output.push('{');
893        let mut first = true;
894
895        // Timestamp
896        if inner.report_timestamp {
897            let ts = (inner.time_function)(SystemTime::now());
898            if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
899                let secs = duration.as_secs();
900                let ts_str = format_timestamp(secs, &inner.time_format);
901                write_json_field(output, keys::TIMESTAMP, &ts_str, &mut first);
902            }
903        }
904
905        // Level
906        write_json_field(output, keys::LEVEL, level.as_str(), &mut first);
907
908        // Prefix
909        if !inner.prefix.is_empty() {
910            write_json_field(output, keys::PREFIX, &inner.prefix, &mut first);
911        }
912
913        // Message
914        if !msg.is_empty() {
915            write_json_field(output, keys::MESSAGE, msg, &mut first);
916        }
917
918        // Default fields
919        for (key, value) in &inner.fields {
920            write_json_field(output, key, value, &mut first);
921        }
922
923        // Additional keyvals
924        for (key, value) in keyvals {
925            write_json_field(output, key, value, &mut first);
926        }
927
928        output.push_str("}\n");
929    }
930
931    /// Format logfmt output without requiring &self.
932    fn format_logfmt_inner(
933        inner: &LoggerInner,
934        level: Level,
935        msg: &str,
936        keyvals: &[(&str, &str)],
937        output: &mut String,
938    ) {
939        let mut first = true;
940
941        // Timestamp
942        if inner.report_timestamp {
943            let ts = (inner.time_function)(SystemTime::now());
944            if let Ok(duration) = ts.duration_since(UNIX_EPOCH) {
945                let secs = duration.as_secs();
946                let ts_str = format_timestamp(secs, &inner.time_format);
947                write_logfmt_field(output, keys::TIMESTAMP, &ts_str, &mut first);
948            }
949        }
950
951        // Level
952        write_logfmt_field(output, keys::LEVEL, level.as_str(), &mut first);
953
954        // Prefix
955        if !inner.prefix.is_empty() {
956            write_logfmt_field(output, keys::PREFIX, &inner.prefix, &mut first);
957        }
958
959        // Message
960        if !msg.is_empty() {
961            write_logfmt_field(output, keys::MESSAGE, msg, &mut first);
962        }
963
964        // Default fields
965        for (key, value) in &inner.fields {
966            write_logfmt_field(output, key, value, &mut first);
967        }
968
969        // Additional keyvals
970        for (key, value) in keyvals {
971            write_logfmt_field(output, key, value, &mut first);
972        }
973
974        output.push('\n');
975    }
976
977    // =========================================================================
978    // Instance method wrappers (for backward compatibility)
979    // =========================================================================
980
981    #[expect(dead_code, reason = "Kept for API compatibility")]
982    fn format_text(
983        &self,
984        inner: &LoggerInner,
985        level: Level,
986        msg: &str,
987        keyvals: &[(&str, &str)],
988        output: &mut String,
989    ) {
990        Self::format_text_inner(inner, level, msg, keyvals, output);
991    }
992
993    #[expect(dead_code, reason = "Kept for API compatibility")]
994    fn format_text_keyval(
995        &self,
996        styles: &Styles,
997        key: &str,
998        value: &str,
999        first: &mut bool,
1000        output: &mut String,
1001    ) {
1002        Self::format_text_keyval_inner(styles, key, value, first, output);
1003    }
1004
1005    #[expect(dead_code, reason = "Kept for API compatibility")]
1006    fn format_json(
1007        &self,
1008        inner: &LoggerInner,
1009        level: Level,
1010        msg: &str,
1011        keyvals: &[(&str, &str)],
1012        output: &mut String,
1013    ) {
1014        Self::format_json_inner(inner, level, msg, keyvals, output);
1015    }
1016
1017    #[expect(dead_code, reason = "Kept for API compatibility")]
1018    fn format_logfmt(
1019        &self,
1020        inner: &LoggerInner,
1021        level: Level,
1022        msg: &str,
1023        keyvals: &[(&str, &str)],
1024        output: &mut String,
1025    ) {
1026        Self::format_logfmt_inner(inner, level, msg, keyvals, output);
1027    }
1028
1029    /// Logs a debug message.
1030    pub fn debug(&self, msg: &str, keyvals: &[(&str, &str)]) {
1031        self.log(Level::Debug, msg, keyvals);
1032    }
1033
1034    /// Logs an info message.
1035    pub fn info(&self, msg: &str, keyvals: &[(&str, &str)]) {
1036        self.log(Level::Info, msg, keyvals);
1037    }
1038
1039    /// Logs a warning message.
1040    pub fn warn(&self, msg: &str, keyvals: &[(&str, &str)]) {
1041        self.log(Level::Warn, msg, keyvals);
1042    }
1043
1044    /// Logs an error message.
1045    pub fn error(&self, msg: &str, keyvals: &[(&str, &str)]) {
1046        self.log(Level::Error, msg, keyvals);
1047    }
1048
1049    /// Logs a fatal message.
1050    pub fn fatal(&self, msg: &str, keyvals: &[(&str, &str)]) {
1051        self.log(Level::Fatal, msg, keyvals);
1052    }
1053
1054    /// Logs a message with formatting.
1055    pub fn logf(&self, level: Level, format: &str, args: &[&dyn fmt::Display]) {
1056        let msg = format_args_simple(format, args);
1057        self.log(level, &msg, &[]);
1058    }
1059
1060    /// Logs a debug message with formatting.
1061    pub fn debugf(&self, format: &str, args: &[&dyn fmt::Display]) {
1062        self.logf(Level::Debug, format, args);
1063    }
1064
1065    /// Logs an info message with formatting.
1066    pub fn infof(&self, format: &str, args: &[&dyn fmt::Display]) {
1067        self.logf(Level::Info, format, args);
1068    }
1069
1070    /// Logs a warning message with formatting.
1071    pub fn warnf(&self, format: &str, args: &[&dyn fmt::Display]) {
1072        self.logf(Level::Warn, format, args);
1073    }
1074
1075    /// Logs an error message with formatting.
1076    pub fn errorf(&self, format: &str, args: &[&dyn fmt::Display]) {
1077        self.logf(Level::Error, format, args);
1078    }
1079
1080    /// Logs a fatal message with formatting.
1081    pub fn fatalf(&self, format: &str, args: &[&dyn fmt::Display]) {
1082        self.logf(Level::Fatal, format, args);
1083    }
1084}
1085
1086/// Simple format string replacement.
1087///
1088/// Replaces each `{}` placeholder in order with the corresponding argument.
1089/// Extra args beyond the number of placeholders are ignored.
1090fn format_args_simple(format: &str, args: &[&dyn fmt::Display]) -> String {
1091    use fmt::Write;
1092
1093    let mut result = String::with_capacity(format.len());
1094    let mut arg_idx = 0;
1095    let mut rest = format;
1096
1097    while let Some(pos) = rest.find("{}") {
1098        result.push_str(&rest[..pos]);
1099        if arg_idx < args.len() {
1100            let _ = write!(result, "{}", args[arg_idx]);
1101            arg_idx += 1;
1102        } else {
1103            result.push_str("{}");
1104        }
1105        rest = &rest[pos + 2..];
1106    }
1107    result.push_str(rest);
1108    result
1109}
1110
1111/// Formats a Unix timestamp.
1112fn format_timestamp(secs: u64, format: &str) -> String {
1113    use chrono::{DateTime, Utc};
1114
1115    if let Some(datetime) = DateTime::from_timestamp(secs as i64, 0) {
1116        datetime.with_timezone(&Utc).format(format).to_string()
1117    } else {
1118        "INVALID TIMESTAMP".to_string()
1119    }
1120}
1121
1122/// Writes a JSON field.
1123fn write_json_field(output: &mut String, key: &str, value: &str, first: &mut bool) {
1124    if !*first {
1125        output.push(',');
1126    }
1127    output.push('"');
1128    output.push_str(&escape_json(key));
1129    output.push_str("\":\"");
1130    output.push_str(&escape_json(value));
1131    output.push('"');
1132    *first = false;
1133}
1134
1135/// Escapes a string for JSON.
1136fn escape_json(s: &str) -> String {
1137    let mut result = String::with_capacity(s.len());
1138    for c in s.chars() {
1139        match c {
1140            '"' => result.push_str("\\\""),
1141            '\\' => result.push_str("\\\\"),
1142            '\n' => result.push_str("\\n"),
1143            '\r' => result.push_str("\\r"),
1144            '\t' => result.push_str("\\t"),
1145            c if c.is_control() => {
1146                let cp = c as u32;
1147                if cp <= 0xFFFF {
1148                    result.push_str(&format!("\\u{cp:04x}"));
1149                } else {
1150                    // Encode as UTF-16 surrogate pair for JSON compatibility
1151                    let s = cp - 0x10000;
1152                    let hi = 0xD800 + (s >> 10);
1153                    let lo = 0xDC00 + (s & 0x3FF);
1154                    result.push_str(&format!("\\u{hi:04x}\\u{lo:04x}"));
1155                }
1156            }
1157            c => result.push(c),
1158        }
1159    }
1160    result
1161}
1162
1163/// Writes a logfmt field.
1164fn write_logfmt_field(output: &mut String, key: &str, value: &str, first: &mut bool) {
1165    if !*first {
1166        output.push(' ');
1167    }
1168    output.push_str(key);
1169    output.push('=');
1170    if needs_quoting(value) {
1171        output.push('"');
1172        output.push_str(&escape_logfmt(value));
1173        output.push('"');
1174    } else {
1175        output.push_str(value);
1176    }
1177    *first = false;
1178}
1179
1180/// Checks if a value needs quoting in logfmt.
1181fn needs_quoting(s: &str) -> bool {
1182    s.is_empty()
1183        || s.chars()
1184            .any(|c| c.is_whitespace() || c == '"' || c == '=' || c.is_control())
1185}
1186
1187/// Escapes a string for logfmt.
1188fn escape_logfmt(s: &str) -> String {
1189    let mut result = String::with_capacity(s.len());
1190    for c in s.chars() {
1191        match c {
1192            '"' => result.push_str("\\\""),
1193            '\\' => result.push_str("\\\\"),
1194            '\n' => result.push_str("\\n"),
1195            '\r' => result.push_str("\\r"),
1196            '\t' => result.push_str("\\t"),
1197            c => result.push(c),
1198        }
1199    }
1200    result
1201}
1202
1203/// Prelude module for convenient imports.
1204pub mod prelude {
1205    pub use crate::{
1206        CallerInfo, DEFAULT_TIME_FORMAT, ErrorHandler, Formatter, Level, Logger, Options,
1207        ParseLevelError, ParseResult, Styles, keys, long_caller_formatter, now_utc,
1208        short_caller_formatter,
1209    };
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214    use super::*;
1215
1216    #[test]
1217    fn test_level_ordering() {
1218        assert!(Level::Debug < Level::Info);
1219        assert!(Level::Info < Level::Warn);
1220        assert!(Level::Warn < Level::Error);
1221        assert!(Level::Error < Level::Fatal);
1222    }
1223
1224    #[test]
1225    fn test_level_display() {
1226        assert_eq!(Level::Debug.to_string(), "debug");
1227        assert_eq!(Level::Info.to_string(), "info");
1228        assert_eq!(Level::Warn.to_string(), "warn");
1229        assert_eq!(Level::Error.to_string(), "error");
1230        assert_eq!(Level::Fatal.to_string(), "fatal");
1231    }
1232
1233    #[test]
1234    fn test_level_parse() {
1235        assert_eq!("debug".parse::<Level>().unwrap(), Level::Debug);
1236        assert_eq!("INFO".parse::<Level>().unwrap(), Level::Info);
1237        assert_eq!("WARN".parse::<Level>().unwrap(), Level::Warn);
1238        // Note: "warning" is NOT accepted - only "warn" (matching Go behavior)
1239        assert!("warning".parse::<Level>().is_err());
1240        assert!("invalid".parse::<Level>().is_err());
1241    }
1242
1243    #[test]
1244    fn test_logger_new() {
1245        let logger = Logger::new();
1246        assert_eq!(logger.level(), Level::Info);
1247        assert!(logger.prefix().is_empty());
1248    }
1249
1250    #[test]
1251    fn test_logger_set_level() {
1252        let logger = Logger::new();
1253        logger.set_level(Level::Debug);
1254        assert_eq!(logger.level(), Level::Debug);
1255    }
1256
1257    #[test]
1258    fn test_logger_set_prefix() {
1259        let logger = Logger::new();
1260        logger.set_prefix("myapp");
1261        assert_eq!(logger.prefix(), "myapp");
1262    }
1263
1264    #[test]
1265    fn test_logger_with_prefix() {
1266        let logger = Logger::new();
1267        let prefixed = logger.with_prefix("myapp");
1268        assert_eq!(prefixed.prefix(), "myapp");
1269        assert!(logger.prefix().is_empty()); // Original unchanged
1270    }
1271
1272    #[test]
1273    fn test_logger_with_fields() {
1274        let logger = Logger::new();
1275        let with_fields = logger.with_fields(&[("app", "test"), ("version", "1.0")]);
1276        // Fields are internal, just verify it doesn't panic
1277        drop(with_fields);
1278    }
1279
1280    #[test]
1281    fn test_logger_with_method() {
1282        // Test the Go API compatible `with()` method
1283        let logger = Logger::new();
1284        let ctx_logger = logger.with(&[("request_id", "abc123"), ("user", "alice")]);
1285        // Verify it creates a new logger (not the same instance)
1286        // Both should work independently
1287        ctx_logger.info("test message", &[]);
1288        logger.info("another message", &[]);
1289    }
1290
1291    #[test]
1292    fn test_caller_info_capture() {
1293        // CallerInfo::capture should return Some when called from a test
1294        let info = CallerInfo::capture(0);
1295        // The capture might return None in optimized builds, so we just
1296        // verify it doesn't panic
1297        if let Some(caller) = info {
1298            // In debug builds, we should get meaningful info
1299            assert!(!caller.function.is_empty());
1300        }
1301    }
1302
1303    #[test]
1304    fn test_styles_default() {
1305        let styles = Styles::new();
1306        assert!(styles.levels.contains_key(&Level::Debug));
1307        assert!(styles.levels.contains_key(&Level::Info));
1308        assert!(styles.levels.contains_key(&Level::Warn));
1309        assert!(styles.levels.contains_key(&Level::Error));
1310        assert!(styles.levels.contains_key(&Level::Fatal));
1311    }
1312
1313    #[test]
1314    fn test_trim_caller_path() {
1315        assert_eq!(trim_caller_path("src/lib.rs", 1), "lib.rs");
1316        assert_eq!(trim_caller_path("foo/bar/baz.rs", 2), "bar/baz.rs");
1317        assert_eq!(trim_caller_path("baz.rs", 2), "baz.rs");
1318        assert_eq!(trim_caller_path("foo/bar/baz.rs", 0), "foo/bar/baz.rs");
1319    }
1320
1321    #[test]
1322    fn test_short_caller_formatter() {
1323        let result = short_caller_formatter("/home/user/project/src/main.rs", 42, "main");
1324        assert!(result.contains(":42"));
1325    }
1326
1327    #[test]
1328    fn test_long_caller_formatter() {
1329        let result = long_caller_formatter("/home/user/project/src/main.rs", 42, "main");
1330        assert_eq!(result, "/home/user/project/src/main.rs:42");
1331    }
1332
1333    #[test]
1334    fn test_escape_json() {
1335        assert_eq!(escape_json("hello"), "hello");
1336        assert_eq!(escape_json("hello \"world\""), "hello \\\"world\\\"");
1337        assert_eq!(escape_json("line1\nline2"), "line1\\nline2");
1338    }
1339
1340    #[test]
1341    fn test_needs_quoting() {
1342        assert!(needs_quoting(""));
1343        assert!(needs_quoting("hello world"));
1344        assert!(needs_quoting("key=value"));
1345        assert!(needs_quoting("has\"quote"));
1346        assert!(!needs_quoting("simple"));
1347    }
1348
1349    #[test]
1350    fn test_escape_logfmt() {
1351        assert_eq!(escape_logfmt("hello"), "hello");
1352        assert_eq!(escape_logfmt("hello \"world\""), "hello \\\"world\\\"");
1353        assert_eq!(escape_logfmt("line1\nline2"), "line1\\nline2");
1354    }
1355
1356    #[test]
1357    fn test_formatter_default() {
1358        assert_eq!(Formatter::default(), Formatter::Text);
1359    }
1360
1361    #[test]
1362    fn test_options_default() {
1363        let opts = Options::default();
1364        assert_eq!(opts.level, Level::Info);
1365        assert_eq!(opts.formatter, Formatter::Text);
1366        assert!(!opts.report_timestamp);
1367        assert!(!opts.report_caller);
1368    }
1369
1370    #[test]
1371    fn test_logger_with_options() {
1372        let opts = Options {
1373            level: Level::Debug,
1374            prefix: "test".to_string(),
1375            report_timestamp: true,
1376            ..Default::default()
1377        };
1378        let logger = Logger::with_options(opts);
1379        assert_eq!(logger.level(), Level::Debug);
1380        assert_eq!(logger.prefix(), "test");
1381    }
1382
1383    /// A writer that always fails for testing error handling.
1384    struct FailingWriter;
1385
1386    impl Write for FailingWriter {
1387        fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
1388            Err(io::Error::other("simulated failure"))
1389        }
1390
1391        fn flush(&mut self) -> io::Result<()> {
1392            Err(io::Error::other("simulated failure"))
1393        }
1394    }
1395
1396    #[test]
1397    fn test_error_handler_called_on_io_failure() {
1398        use std::sync::atomic::{AtomicUsize, Ordering};
1399
1400        let error_count = Arc::new(AtomicUsize::new(0));
1401        let counter = error_count.clone();
1402
1403        // Create logger with custom error handler
1404        let logger = Logger::new().with_error_handler(move |_err| {
1405            counter.fetch_add(1, Ordering::Relaxed);
1406        });
1407
1408        // Replace writer with failing writer
1409        {
1410            let mut inner = logger.inner.write().unwrap();
1411            inner.writer = Box::new(FailingWriter);
1412        }
1413
1414        // Log a message - should trigger error handler
1415        logger.info("test message", &[]);
1416
1417        // Verify error handler was called
1418        assert_eq!(error_count.load(Ordering::Relaxed), 1);
1419    }
1420
1421    #[test]
1422    fn test_error_handler_receives_correct_error() {
1423        use std::sync::Mutex;
1424
1425        let captured_error = Arc::new(Mutex::new(None::<String>));
1426        let error_capture = captured_error.clone();
1427
1428        let logger = Logger::new().with_error_handler(move |err| {
1429            *error_capture.lock().unwrap() = Some(err.to_string());
1430        });
1431
1432        // Replace writer with failing writer
1433        {
1434            let mut inner = logger.inner.write().unwrap();
1435            inner.writer = Box::new(FailingWriter);
1436        }
1437
1438        logger.info("test", &[]);
1439
1440        let error_msg = captured_error.lock().unwrap();
1441        assert!(error_msg.is_some());
1442        assert!(error_msg.as_ref().unwrap().contains("simulated failure"));
1443    }
1444
1445    #[test]
1446    fn test_default_behavior_warns_once() {
1447        // Without an error handler, the default behavior is to warn once
1448        // We can't easily test stderr output, but we can verify it doesn't panic
1449        let logger = Logger::new();
1450
1451        // Replace writer with failing writer
1452        {
1453            let mut inner = logger.inner.write().unwrap();
1454            inner.writer = Box::new(FailingWriter);
1455        }
1456
1457        // Log multiple messages - should not panic
1458        logger.info("first message", &[]);
1459        logger.info("second message", &[]);
1460        logger.info("third message", &[]);
1461
1462        // Verify has_warned_io_failure is set
1463        let inner = logger.inner.read().unwrap();
1464        assert!(inner.has_warned_io_failure);
1465    }
1466
1467    #[test]
1468    fn test_error_handler_inherited_by_with_fields() {
1469        use std::sync::atomic::{AtomicUsize, Ordering};
1470
1471        let error_count = Arc::new(AtomicUsize::new(0));
1472        let counter = error_count.clone();
1473
1474        let logger = Logger::new().with_error_handler(move |_err| {
1475            counter.fetch_add(1, Ordering::Relaxed);
1476        });
1477
1478        // Create a child logger with additional fields
1479        let child_logger = logger.with_fields(&[("component", "test")]);
1480
1481        // Replace writer with failing writer on child
1482        {
1483            let mut inner = child_logger.inner.write().unwrap();
1484            inner.writer = Box::new(FailingWriter);
1485        }
1486
1487        // Log a message on child - should use inherited error handler
1488        child_logger.info("test message", &[]);
1489
1490        assert_eq!(error_count.load(Ordering::Relaxed), 1);
1491    }
1492
1493    #[test]
1494    fn test_with_error_handler_returns_same_logger() {
1495        use std::sync::atomic::{AtomicBool, Ordering};
1496
1497        let called = Arc::new(AtomicBool::new(false));
1498        let flag = called.clone();
1499
1500        let logger = Logger::new().with_error_handler(move |_| {
1501            flag.store(true, Ordering::Relaxed);
1502        });
1503
1504        // Verify the logger is usable after setting error handler
1505        assert_eq!(logger.level(), Level::Info);
1506
1507        // The error handler should be set
1508        let inner = logger.inner.read().unwrap();
1509        assert!(inner.error_handler.is_some());
1510    }
1511
1512    #[test]
1513    fn test_caller_warning_flag_set_on_first_log() {
1514        let logger = Logger::new();
1515        logger.set_report_caller(true);
1516
1517        // Verify warning flag is not set initially
1518        {
1519            let inner = logger.inner.read().unwrap();
1520            assert!(!inner.warned_caller_overhead);
1521        }
1522
1523        // Log a message (this should set the warning flag)
1524        logger.info("test message", &[]);
1525
1526        // Verify warning flag is now set
1527        {
1528            let inner = logger.inner.read().unwrap();
1529            assert!(inner.warned_caller_overhead);
1530        }
1531    }
1532
1533    #[test]
1534    fn test_caller_warning_suppressed() {
1535        let logger = Logger::new();
1536        logger.set_report_caller(true);
1537        logger.suppress_caller_warning();
1538
1539        // Verify suppression flag is set
1540        {
1541            let inner = logger.inner.read().unwrap();
1542            assert!(inner.suppress_caller_warning);
1543        }
1544
1545        // Log a message (warning flag should still be false since suppressed)
1546        logger.info("test message", &[]);
1547
1548        // Verify warning flag remains false when suppressed
1549        {
1550            let inner = logger.inner.read().unwrap();
1551            assert!(!inner.warned_caller_overhead);
1552        }
1553    }
1554
1555    #[test]
1556    fn test_caller_warning_not_triggered_when_caller_disabled() {
1557        let logger = Logger::new();
1558        // report_caller is false by default
1559
1560        // Log a message
1561        logger.info("test message", &[]);
1562
1563        // Verify warning flag is not set when caller reporting is disabled
1564        {
1565            let inner = logger.inner.read().unwrap();
1566            assert!(!inner.warned_caller_overhead);
1567        }
1568    }
1569
1570    #[test]
1571    fn test_caller_warning_inherits_suppression_via_with_fields() {
1572        let logger = Logger::new();
1573        logger.set_report_caller(true);
1574        logger.suppress_caller_warning();
1575
1576        // Create child logger
1577        let child = logger.with_fields(&[("key", "value")]);
1578
1579        // Verify child inherits suppression setting
1580        {
1581            let inner = child.inner.read().unwrap();
1582            assert!(inner.suppress_caller_warning);
1583        }
1584    }
1585
1586    #[test]
1587    fn test_caller_warning_resets_for_child_logger() {
1588        let logger = Logger::new();
1589        logger.set_report_caller(true);
1590
1591        // Trigger warning on parent
1592        logger.info("parent message", &[]);
1593
1594        // Create child logger
1595        let child = logger.with_fields(&[("key", "value")]);
1596
1597        // Verify child has fresh warning state
1598        {
1599            let inner = child.inner.read().unwrap();
1600            assert!(!inner.warned_caller_overhead);
1601        }
1602    }
1603}