ringkernel_core/
logging.rs

1//! Structured logging with trace correlation.
2//!
3//! This module provides enterprise-grade logging infrastructure with:
4//! - Automatic trace ID and span ID injection
5//! - JSON structured output for log aggregation
6//! - Log level filtering by module
7//! - Integration with the `tracing` crate
8//!
9//! # Example
10//!
11//! ```ignore
12//! use ringkernel_core::logging::{LogConfig, StructuredLogger, LogOutput};
13//!
14//! let config = LogConfig::builder()
15//!     .level(LogLevel::Info)
16//!     .output(LogOutput::Json)
17//!     .with_trace_correlation(true)
18//!     .build();
19//!
20//! let logger = StructuredLogger::new(config);
21//! logger.info("Kernel started", &[("kernel_id", "k1"), ("mode", "persistent")]);
22//! ```
23
24use parking_lot::RwLock;
25use std::collections::HashMap;
26use std::fmt;
27use std::io::Write;
28use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
29use std::sync::Arc;
30use std::time::{Instant, SystemTime, UNIX_EPOCH};
31
32use crate::observability::{SpanId, TraceId};
33
34// ============================================================================
35// Log Level
36// ============================================================================
37
38/// Log severity levels.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
40pub enum LogLevel {
41    /// Trace-level logging (most verbose).
42    Trace = 0,
43    /// Debug-level logging.
44    Debug = 1,
45    /// Info-level logging.
46    #[default]
47    Info = 2,
48    /// Warning-level logging.
49    Warn = 3,
50    /// Error-level logging.
51    Error = 4,
52    /// Fatal-level logging (least verbose).
53    Fatal = 5,
54}
55
56impl LogLevel {
57    /// Convert to string representation.
58    pub fn as_str(&self) -> &'static str {
59        match self {
60            LogLevel::Trace => "TRACE",
61            LogLevel::Debug => "DEBUG",
62            LogLevel::Info => "INFO",
63            LogLevel::Warn => "WARN",
64            LogLevel::Error => "ERROR",
65            LogLevel::Fatal => "FATAL",
66        }
67    }
68
69    /// Parse log level from string representation.
70    pub fn parse(s: &str) -> Option<Self> {
71        match s.to_uppercase().as_str() {
72            "TRACE" => Some(LogLevel::Trace),
73            "DEBUG" => Some(LogLevel::Debug),
74            "INFO" => Some(LogLevel::Info),
75            "WARN" | "WARNING" => Some(LogLevel::Warn),
76            "ERROR" => Some(LogLevel::Error),
77            "FATAL" | "CRITICAL" => Some(LogLevel::Fatal),
78            _ => None,
79        }
80    }
81}
82
83impl fmt::Display for LogLevel {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(f, "{}", self.as_str())
86    }
87}
88
89// ============================================================================
90// Log Output Format
91// ============================================================================
92
93/// Log output format.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum LogOutput {
96    /// Human-readable text format.
97    #[default]
98    Text,
99    /// JSON structured format for log aggregation.
100    Json,
101    /// Compact single-line format.
102    Compact,
103    /// Pretty-printed format with colors (if terminal supports it).
104    Pretty,
105}
106
107// ============================================================================
108// Trace Context
109// ============================================================================
110
111/// Thread-local trace context for correlation.
112#[derive(Debug, Clone)]
113pub struct TraceContext {
114    /// Current trace ID.
115    pub trace_id: Option<TraceId>,
116    /// Current span ID.
117    pub span_id: Option<SpanId>,
118    /// Parent span ID.
119    pub parent_span_id: Option<SpanId>,
120    /// Additional context fields.
121    pub fields: HashMap<String, String>,
122}
123
124impl TraceContext {
125    /// Create a new empty trace context.
126    pub fn new() -> Self {
127        Self {
128            trace_id: None,
129            span_id: None,
130            parent_span_id: None,
131            fields: HashMap::new(),
132        }
133    }
134
135    /// Create a trace context with a new trace.
136    pub fn with_new_trace() -> Self {
137        Self {
138            trace_id: Some(TraceId::new()),
139            span_id: Some(SpanId::new()),
140            parent_span_id: None,
141            fields: HashMap::new(),
142        }
143    }
144
145    /// Set trace ID.
146    pub fn with_trace_id(mut self, trace_id: TraceId) -> Self {
147        self.trace_id = Some(trace_id);
148        self
149    }
150
151    /// Set span ID.
152    pub fn with_span_id(mut self, span_id: SpanId) -> Self {
153        self.span_id = Some(span_id);
154        self
155    }
156
157    /// Add a context field.
158    pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
159        self.fields.insert(key.into(), value.into());
160        self
161    }
162}
163
164impl Default for TraceContext {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170// ============================================================================
171// Log Configuration
172// ============================================================================
173
174/// Configuration for structured logging.
175#[derive(Debug, Clone)]
176pub struct LogConfig {
177    /// Default log level.
178    pub level: LogLevel,
179    /// Output format.
180    pub output: LogOutput,
181    /// Enable trace correlation.
182    pub trace_correlation: bool,
183    /// Include timestamps.
184    pub include_timestamps: bool,
185    /// Include caller location (file:line).
186    pub include_location: bool,
187    /// Include thread ID.
188    pub include_thread_id: bool,
189    /// Per-module log levels.
190    pub module_levels: HashMap<String, LogLevel>,
191    /// Service name for structured logs.
192    pub service_name: String,
193    /// Environment (dev, staging, prod).
194    pub environment: String,
195    /// Custom fields added to every log.
196    pub global_fields: HashMap<String, String>,
197}
198
199impl Default for LogConfig {
200    fn default() -> Self {
201        Self {
202            level: LogLevel::Info,
203            output: LogOutput::Text,
204            trace_correlation: true,
205            include_timestamps: true,
206            include_location: false,
207            include_thread_id: false,
208            module_levels: HashMap::new(),
209            service_name: "ringkernel".to_string(),
210            environment: "development".to_string(),
211            global_fields: HashMap::new(),
212        }
213    }
214}
215
216impl LogConfig {
217    /// Create a new builder.
218    pub fn builder() -> LogConfigBuilder {
219        LogConfigBuilder::new()
220    }
221
222    /// Create a development configuration.
223    pub fn development() -> Self {
224        Self {
225            level: LogLevel::Debug,
226            output: LogOutput::Pretty,
227            trace_correlation: true,
228            include_timestamps: true,
229            include_location: true,
230            include_thread_id: false,
231            environment: "development".to_string(),
232            ..Default::default()
233        }
234    }
235
236    /// Create a production configuration.
237    pub fn production() -> Self {
238        Self {
239            level: LogLevel::Info,
240            output: LogOutput::Json,
241            trace_correlation: true,
242            include_timestamps: true,
243            include_location: false,
244            include_thread_id: true,
245            environment: "production".to_string(),
246            ..Default::default()
247        }
248    }
249
250    /// Get effective log level for a module.
251    pub fn effective_level(&self, module: &str) -> LogLevel {
252        // Check for exact match first
253        if let Some(&level) = self.module_levels.get(module) {
254            return level;
255        }
256
257        // Find the longest (most specific) prefix match
258        let mut best_match: Option<(&str, LogLevel)> = None;
259        for (prefix, &level) in &self.module_levels {
260            if module.starts_with(prefix) {
261                match best_match {
262                    None => best_match = Some((prefix, level)),
263                    Some((best_prefix, _)) if prefix.len() > best_prefix.len() => {
264                        best_match = Some((prefix, level));
265                    }
266                    _ => {}
267                }
268            }
269        }
270
271        best_match.map(|(_, level)| level).unwrap_or(self.level)
272    }
273}
274
275/// Builder for log configuration.
276#[derive(Debug, Default)]
277pub struct LogConfigBuilder {
278    config: LogConfig,
279}
280
281impl LogConfigBuilder {
282    /// Create a new builder.
283    pub fn new() -> Self {
284        Self {
285            config: LogConfig::default(),
286        }
287    }
288
289    /// Set default log level.
290    pub fn level(mut self, level: LogLevel) -> Self {
291        self.config.level = level;
292        self
293    }
294
295    /// Set output format.
296    pub fn output(mut self, output: LogOutput) -> Self {
297        self.config.output = output;
298        self
299    }
300
301    /// Enable/disable trace correlation.
302    pub fn with_trace_correlation(mut self, enabled: bool) -> Self {
303        self.config.trace_correlation = enabled;
304        self
305    }
306
307    /// Enable/disable timestamps.
308    pub fn with_timestamps(mut self, enabled: bool) -> Self {
309        self.config.include_timestamps = enabled;
310        self
311    }
312
313    /// Enable/disable caller location.
314    pub fn with_location(mut self, enabled: bool) -> Self {
315        self.config.include_location = enabled;
316        self
317    }
318
319    /// Enable/disable thread ID.
320    pub fn with_thread_id(mut self, enabled: bool) -> Self {
321        self.config.include_thread_id = enabled;
322        self
323    }
324
325    /// Set service name.
326    pub fn service_name(mut self, name: impl Into<String>) -> Self {
327        self.config.service_name = name.into();
328        self
329    }
330
331    /// Set environment.
332    pub fn environment(mut self, env: impl Into<String>) -> Self {
333        self.config.environment = env.into();
334        self
335    }
336
337    /// Set log level for a specific module.
338    pub fn module_level(mut self, module: impl Into<String>, level: LogLevel) -> Self {
339        self.config.module_levels.insert(module.into(), level);
340        self
341    }
342
343    /// Add a global field.
344    pub fn global_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
345        self.config.global_fields.insert(key.into(), value.into());
346        self
347    }
348
349    /// Build the configuration.
350    pub fn build(self) -> LogConfig {
351        self.config
352    }
353}
354
355// ============================================================================
356// Log Entry
357// ============================================================================
358
359/// A structured log entry.
360#[derive(Debug, Clone)]
361pub struct LogEntry {
362    /// Log level.
363    pub level: LogLevel,
364    /// Log message.
365    pub message: String,
366    /// Timestamp.
367    pub timestamp: SystemTime,
368    /// Module/target.
369    pub target: Option<String>,
370    /// File name.
371    pub file: Option<String>,
372    /// Line number.
373    pub line: Option<u32>,
374    /// Thread ID.
375    pub thread_id: Option<u64>,
376    /// Thread name.
377    pub thread_name: Option<String>,
378    /// Trace ID.
379    pub trace_id: Option<TraceId>,
380    /// Span ID.
381    pub span_id: Option<SpanId>,
382    /// Structured fields.
383    pub fields: HashMap<String, LogValue>,
384}
385
386/// Log field value types.
387#[derive(Debug, Clone)]
388pub enum LogValue {
389    /// String value.
390    String(String),
391    /// Integer value.
392    Int(i64),
393    /// Unsigned integer value.
394    Uint(u64),
395    /// Float value.
396    Float(f64),
397    /// Boolean value.
398    Bool(bool),
399}
400
401impl fmt::Display for LogValue {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        match self {
404            LogValue::String(s) => write!(f, "{}", s),
405            LogValue::Int(i) => write!(f, "{}", i),
406            LogValue::Uint(u) => write!(f, "{}", u),
407            LogValue::Float(fl) => write!(f, "{}", fl),
408            LogValue::Bool(b) => write!(f, "{}", b),
409        }
410    }
411}
412
413impl From<&str> for LogValue {
414    fn from(s: &str) -> Self {
415        LogValue::String(s.to_string())
416    }
417}
418
419impl From<String> for LogValue {
420    fn from(s: String) -> Self {
421        LogValue::String(s)
422    }
423}
424
425impl From<i64> for LogValue {
426    fn from(i: i64) -> Self {
427        LogValue::Int(i)
428    }
429}
430
431impl From<u64> for LogValue {
432    fn from(u: u64) -> Self {
433        LogValue::Uint(u)
434    }
435}
436
437impl From<f64> for LogValue {
438    fn from(f: f64) -> Self {
439        LogValue::Float(f)
440    }
441}
442
443impl From<bool> for LogValue {
444    fn from(b: bool) -> Self {
445        LogValue::Bool(b)
446    }
447}
448
449impl LogEntry {
450    /// Create a new log entry.
451    pub fn new(level: LogLevel, message: impl Into<String>) -> Self {
452        Self {
453            level,
454            message: message.into(),
455            timestamp: SystemTime::now(),
456            target: None,
457            file: None,
458            line: None,
459            thread_id: None,
460            thread_name: None,
461            trace_id: None,
462            span_id: None,
463            fields: HashMap::new(),
464        }
465    }
466
467    /// Set target/module.
468    pub fn with_target(mut self, target: impl Into<String>) -> Self {
469        self.target = Some(target.into());
470        self
471    }
472
473    /// Set trace context.
474    pub fn with_trace_context(mut self, ctx: &TraceContext) -> Self {
475        self.trace_id = ctx.trace_id;
476        self.span_id = ctx.span_id;
477        for (k, v) in &ctx.fields {
478            self.fields.insert(k.clone(), LogValue::String(v.clone()));
479        }
480        self
481    }
482
483    /// Add a field.
484    pub fn with_field(mut self, key: impl Into<String>, value: impl Into<LogValue>) -> Self {
485        self.fields.insert(key.into(), value.into());
486        self
487    }
488
489    /// Format as JSON.
490    pub fn to_json(&self, config: &LogConfig) -> String {
491        let mut json = String::with_capacity(512);
492        json.push('{');
493
494        // Timestamp
495        if config.include_timestamps {
496            let ts = self
497                .timestamp
498                .duration_since(UNIX_EPOCH)
499                .map(|d| d.as_millis())
500                .unwrap_or(0);
501            json.push_str(&format!(r#""timestamp":{},"#, ts));
502        }
503
504        // Level
505        json.push_str(&format!(r#""level":"{}","#, self.level.as_str()));
506
507        // Message (escape quotes)
508        let escaped_msg = self.message.replace('\\', "\\\\").replace('"', "\\\"");
509        json.push_str(&format!(r#""message":"{}","#, escaped_msg));
510
511        // Service and environment
512        json.push_str(&format!(r#""service":"{}","#, config.service_name));
513        json.push_str(&format!(r#""environment":"{}","#, config.environment));
514
515        // Target
516        if let Some(ref target) = self.target {
517            json.push_str(&format!(r#""target":"{}","#, target));
518        }
519
520        // Location
521        if config.include_location {
522            if let Some(ref file) = self.file {
523                json.push_str(&format!(r#""file":"{}","#, file));
524            }
525            if let Some(line) = self.line {
526                json.push_str(&format!(r#""line":{},"#, line));
527            }
528        }
529
530        // Thread
531        if config.include_thread_id {
532            if let Some(tid) = self.thread_id {
533                json.push_str(&format!(r#""thread_id":{},"#, tid));
534            }
535            if let Some(ref name) = self.thread_name {
536                json.push_str(&format!(r#""thread_name":"{}","#, name));
537            }
538        }
539
540        // Trace correlation
541        if config.trace_correlation {
542            if let Some(trace_id) = self.trace_id {
543                json.push_str(&format!(r#""trace_id":"{:032x}","#, trace_id.0));
544            }
545            if let Some(span_id) = self.span_id {
546                json.push_str(&format!(r#""span_id":"{:016x}","#, span_id.0));
547            }
548        }
549
550        // Global fields
551        for (k, v) in &config.global_fields {
552            json.push_str(&format!(r#""{}":"{}","#, k, v));
553        }
554
555        // Entry fields
556        if !self.fields.is_empty() {
557            json.push_str(r#""fields":{"#);
558            let mut first = true;
559            for (k, v) in &self.fields {
560                if !first {
561                    json.push(',');
562                }
563                first = false;
564                match v {
565                    LogValue::String(s) => {
566                        let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
567                        json.push_str(&format!(r#""{}":"{}""#, k, escaped));
568                    }
569                    LogValue::Int(i) => json.push_str(&format!(r#""{}":{}""#, k, i)),
570                    LogValue::Uint(u) => json.push_str(&format!(r#""{}":{}""#, k, u)),
571                    LogValue::Float(f) => json.push_str(&format!(r#""{}":{}""#, k, f)),
572                    LogValue::Bool(b) => json.push_str(&format!(r#""{}":{}""#, k, b)),
573                }
574            }
575            json.push_str("},");
576        }
577
578        // Remove trailing comma and close
579        if json.ends_with(',') {
580            json.pop();
581        }
582        json.push('}');
583
584        json
585    }
586
587    /// Format as text.
588    pub fn to_text(&self, config: &LogConfig) -> String {
589        let mut text = String::with_capacity(256);
590
591        // Timestamp
592        if config.include_timestamps {
593            let ts = self
594                .timestamp
595                .duration_since(UNIX_EPOCH)
596                .map(|d| {
597                    let secs = d.as_secs();
598                    let millis = d.subsec_millis();
599                    format!(
600                        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
601                        1970 + secs / 31536000,            // Approximate year
602                        ((secs % 31536000) / 2592000) + 1, // Month (approximate)
603                        ((secs % 2592000) / 86400) + 1,    // Day
604                        (secs % 86400) / 3600,             // Hour
605                        (secs % 3600) / 60,                // Minute
606                        secs % 60,                         // Second
607                        millis
608                    )
609                })
610                .unwrap_or_else(|_| "1970-01-01T00:00:00.000Z".to_string());
611            text.push_str(&ts);
612            text.push(' ');
613        }
614
615        // Level
616        text.push_str(&format!("{:5} ", self.level.as_str()));
617
618        // Target
619        if let Some(ref target) = self.target {
620            text.push_str(&format!("[{}] ", target));
621        }
622
623        // Trace correlation
624        if config.trace_correlation {
625            if let Some(trace_id) = self.trace_id {
626                text.push_str(&format!("trace={:032x} ", trace_id.0));
627            }
628        }
629
630        // Message
631        text.push_str(&self.message);
632
633        // Fields
634        if !self.fields.is_empty() {
635            text.push_str(" {");
636            let mut first = true;
637            for (k, v) in &self.fields {
638                if !first {
639                    text.push_str(", ");
640                }
641                first = false;
642                text.push_str(&format!("{}={}", k, v));
643            }
644            text.push('}');
645        }
646
647        text
648    }
649}
650
651// ============================================================================
652// Structured Logger
653// ============================================================================
654
655/// Structured logger with trace correlation.
656pub struct StructuredLogger {
657    /// Configuration.
658    config: RwLock<LogConfig>,
659    /// Current trace context.
660    context: RwLock<TraceContext>,
661    /// Log counter.
662    log_count: AtomicU64,
663    /// Error counter.
664    error_count: AtomicU64,
665    /// Enabled flag.
666    enabled: AtomicBool,
667    /// Start time.
668    start_time: Instant,
669    /// Log sinks.
670    sinks: RwLock<Vec<Arc<dyn LogSink>>>,
671}
672
673impl StructuredLogger {
674    /// Create a new logger with configuration.
675    pub fn new(config: LogConfig) -> Self {
676        Self {
677            config: RwLock::new(config),
678            context: RwLock::new(TraceContext::new()),
679            log_count: AtomicU64::new(0),
680            error_count: AtomicU64::new(0),
681            enabled: AtomicBool::new(true),
682            start_time: Instant::now(),
683            sinks: RwLock::new(vec![]),
684        }
685    }
686
687    /// Create with default configuration.
688    pub fn default_logger() -> Self {
689        Self::new(LogConfig::default())
690    }
691
692    /// Create a development logger.
693    pub fn development() -> Self {
694        Self::new(LogConfig::development())
695    }
696
697    /// Create a production logger.
698    pub fn production() -> Self {
699        Self::new(LogConfig::production())
700    }
701
702    /// Enable/disable logging.
703    pub fn set_enabled(&self, enabled: bool) {
704        self.enabled.store(enabled, Ordering::SeqCst);
705    }
706
707    /// Check if logging is enabled.
708    pub fn is_enabled(&self) -> bool {
709        self.enabled.load(Ordering::SeqCst)
710    }
711
712    /// Update configuration.
713    pub fn set_config(&self, config: LogConfig) {
714        *self.config.write() = config;
715    }
716
717    /// Get current configuration.
718    pub fn config(&self) -> LogConfig {
719        self.config.read().clone()
720    }
721
722    /// Set trace context.
723    pub fn set_context(&self, context: TraceContext) {
724        *self.context.write() = context;
725    }
726
727    /// Get current trace context.
728    pub fn context(&self) -> TraceContext {
729        self.context.read().clone()
730    }
731
732    /// Start a new trace.
733    pub fn start_trace(&self) -> TraceContext {
734        let ctx = TraceContext::with_new_trace();
735        *self.context.write() = ctx.clone();
736        ctx
737    }
738
739    /// Add a log sink.
740    pub fn add_sink(&self, sink: Arc<dyn LogSink>) {
741        self.sinks.write().push(sink);
742    }
743
744    /// Log at specified level.
745    pub fn log(&self, level: LogLevel, message: &str, fields: &[(&str, &str)]) {
746        if !self.enabled.load(Ordering::SeqCst) {
747            return;
748        }
749
750        let config = self.config.read();
751        if level < config.level {
752            return;
753        }
754
755        let ctx = self.context.read();
756        let mut entry = LogEntry::new(level, message).with_trace_context(&ctx);
757
758        for (k, v) in fields {
759            entry = entry.with_field(*k, *v);
760        }
761
762        self.log_count.fetch_add(1, Ordering::Relaxed);
763        if level >= LogLevel::Error {
764            self.error_count.fetch_add(1, Ordering::Relaxed);
765        }
766
767        // Format and output
768        let output = match config.output {
769            LogOutput::Json => entry.to_json(&config),
770            LogOutput::Text | LogOutput::Compact | LogOutput::Pretty => entry.to_text(&config),
771        };
772
773        drop(config);
774
775        // Send to sinks
776        let sinks = self.sinks.read();
777        for sink in sinks.iter() {
778            let _ = sink.write(&entry, &output);
779        }
780
781        // Default output to stderr
782        if sinks.is_empty() {
783            let _ = writeln!(std::io::stderr(), "{}", output);
784        }
785    }
786
787    /// Log at trace level.
788    pub fn trace(&self, message: &str, fields: &[(&str, &str)]) {
789        self.log(LogLevel::Trace, message, fields);
790    }
791
792    /// Log at debug level.
793    pub fn debug(&self, message: &str, fields: &[(&str, &str)]) {
794        self.log(LogLevel::Debug, message, fields);
795    }
796
797    /// Log at info level.
798    pub fn info(&self, message: &str, fields: &[(&str, &str)]) {
799        self.log(LogLevel::Info, message, fields);
800    }
801
802    /// Log at warn level.
803    pub fn warn(&self, message: &str, fields: &[(&str, &str)]) {
804        self.log(LogLevel::Warn, message, fields);
805    }
806
807    /// Log at error level.
808    pub fn error(&self, message: &str, fields: &[(&str, &str)]) {
809        self.log(LogLevel::Error, message, fields);
810    }
811
812    /// Log at fatal level.
813    pub fn fatal(&self, message: &str, fields: &[(&str, &str)]) {
814        self.log(LogLevel::Fatal, message, fields);
815    }
816
817    /// Get statistics.
818    pub fn stats(&self) -> LoggerStats {
819        LoggerStats {
820            log_count: self.log_count.load(Ordering::Relaxed),
821            error_count: self.error_count.load(Ordering::Relaxed),
822            uptime: self.start_time.elapsed(),
823            sink_count: self.sinks.read().len(),
824        }
825    }
826}
827
828impl Default for StructuredLogger {
829    fn default() -> Self {
830        Self::default_logger()
831    }
832}
833
834/// Logger statistics.
835#[derive(Debug, Clone)]
836pub struct LoggerStats {
837    /// Total log entries.
838    pub log_count: u64,
839    /// Error-level entries.
840    pub error_count: u64,
841    /// Time since logger started.
842    pub uptime: std::time::Duration,
843    /// Number of sinks.
844    pub sink_count: usize,
845}
846
847// ============================================================================
848// Log Sinks
849// ============================================================================
850
851/// Trait for log output destinations.
852pub trait LogSink: Send + Sync {
853    /// Write a log entry.
854    fn write(&self, entry: &LogEntry, formatted: &str) -> Result<(), LogSinkError>;
855
856    /// Flush buffered logs.
857    fn flush(&self) -> Result<(), LogSinkError> {
858        Ok(())
859    }
860
861    /// Get sink name.
862    fn name(&self) -> &str;
863}
864
865/// Log sink error.
866#[derive(Debug)]
867pub struct LogSinkError {
868    /// Error message.
869    pub message: String,
870}
871
872impl fmt::Display for LogSinkError {
873    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
874        write!(f, "LogSinkError: {}", self.message)
875    }
876}
877
878impl std::error::Error for LogSinkError {}
879
880/// Console log sink.
881pub struct ConsoleSink {
882    /// Use stderr instead of stdout.
883    use_stderr: bool,
884}
885
886impl ConsoleSink {
887    /// Create a new console sink.
888    pub fn new() -> Self {
889        Self { use_stderr: true }
890    }
891
892    /// Create a sink that outputs to stdout.
893    pub fn stdout() -> Self {
894        Self { use_stderr: false }
895    }
896
897    /// Create a sink that outputs to stderr.
898    pub fn stderr() -> Self {
899        Self { use_stderr: true }
900    }
901}
902
903impl Default for ConsoleSink {
904    fn default() -> Self {
905        Self::new()
906    }
907}
908
909impl LogSink for ConsoleSink {
910    fn write(&self, _entry: &LogEntry, formatted: &str) -> Result<(), LogSinkError> {
911        let result = if self.use_stderr {
912            writeln!(std::io::stderr(), "{}", formatted)
913        } else {
914            writeln!(std::io::stdout(), "{}", formatted)
915        };
916        result.map_err(|e| LogSinkError {
917            message: e.to_string(),
918        })
919    }
920
921    fn name(&self) -> &str {
922        if self.use_stderr {
923            "console:stderr"
924        } else {
925            "console:stdout"
926        }
927    }
928}
929
930/// Memory log sink for testing.
931pub struct MemoryLogSink {
932    /// Stored logs.
933    logs: RwLock<Vec<String>>,
934    /// Maximum capacity.
935    capacity: usize,
936}
937
938impl MemoryLogSink {
939    /// Create a new memory sink.
940    pub fn new(capacity: usize) -> Self {
941        Self {
942            logs: RwLock::new(Vec::with_capacity(capacity)),
943            capacity,
944        }
945    }
946
947    /// Get all stored logs.
948    pub fn logs(&self) -> Vec<String> {
949        self.logs.read().clone()
950    }
951
952    /// Clear stored logs.
953    pub fn clear(&self) {
954        self.logs.write().clear();
955    }
956
957    /// Get log count.
958    pub fn len(&self) -> usize {
959        self.logs.read().len()
960    }
961
962    /// Check if empty.
963    pub fn is_empty(&self) -> bool {
964        self.logs.read().is_empty()
965    }
966}
967
968impl LogSink for MemoryLogSink {
969    fn write(&self, _entry: &LogEntry, formatted: &str) -> Result<(), LogSinkError> {
970        let mut logs = self.logs.write();
971        if logs.len() >= self.capacity {
972            logs.remove(0);
973        }
974        logs.push(formatted.to_string());
975        Ok(())
976    }
977
978    fn name(&self) -> &str {
979        "memory"
980    }
981}
982
983/// File log sink.
984pub struct FileLogSink {
985    /// File path.
986    path: String,
987    /// File handle.
988    file: RwLock<Option<std::fs::File>>,
989}
990
991impl FileLogSink {
992    /// Create a new file sink.
993    pub fn new(path: impl Into<String>) -> Result<Self, LogSinkError> {
994        let path = path.into();
995        let file = std::fs::OpenOptions::new()
996            .create(true)
997            .append(true)
998            .open(&path)
999            .map_err(|e| LogSinkError {
1000                message: format!("Failed to open log file: {}", e),
1001            })?;
1002
1003        Ok(Self {
1004            path,
1005            file: RwLock::new(Some(file)),
1006        })
1007    }
1008}
1009
1010impl LogSink for FileLogSink {
1011    fn write(&self, _entry: &LogEntry, formatted: &str) -> Result<(), LogSinkError> {
1012        let mut guard = self.file.write();
1013        if let Some(ref mut file) = *guard {
1014            writeln!(file, "{}", formatted).map_err(|e| LogSinkError {
1015                message: e.to_string(),
1016            })?;
1017        }
1018        Ok(())
1019    }
1020
1021    fn flush(&self) -> Result<(), LogSinkError> {
1022        let mut guard = self.file.write();
1023        if let Some(ref mut file) = *guard {
1024            file.flush().map_err(|e| LogSinkError {
1025                message: e.to_string(),
1026            })?;
1027        }
1028        Ok(())
1029    }
1030
1031    fn name(&self) -> &str {
1032        &self.path
1033    }
1034}
1035
1036// ============================================================================
1037// Global Logger
1038// ============================================================================
1039
1040use std::sync::OnceLock;
1041
1042static GLOBAL_LOGGER: OnceLock<StructuredLogger> = OnceLock::new();
1043
1044/// Initialize the global logger.
1045pub fn init(config: LogConfig) {
1046    let _ = GLOBAL_LOGGER.set(StructuredLogger::new(config));
1047}
1048
1049/// Get the global logger.
1050pub fn logger() -> &'static StructuredLogger {
1051    GLOBAL_LOGGER.get_or_init(StructuredLogger::default_logger)
1052}
1053
1054/// Log at trace level.
1055pub fn trace(message: &str, fields: &[(&str, &str)]) {
1056    logger().trace(message, fields);
1057}
1058
1059/// Log at debug level.
1060pub fn debug(message: &str, fields: &[(&str, &str)]) {
1061    logger().debug(message, fields);
1062}
1063
1064/// Log at info level.
1065pub fn info(message: &str, fields: &[(&str, &str)]) {
1066    logger().info(message, fields);
1067}
1068
1069/// Log at warn level.
1070pub fn warn(message: &str, fields: &[(&str, &str)]) {
1071    logger().warn(message, fields);
1072}
1073
1074/// Log at error level.
1075pub fn error(message: &str, fields: &[(&str, &str)]) {
1076    logger().error(message, fields);
1077}
1078
1079/// Log at fatal level.
1080pub fn fatal(message: &str, fields: &[(&str, &str)]) {
1081    logger().fatal(message, fields);
1082}
1083
1084// ============================================================================
1085// Tests
1086// ============================================================================
1087
1088#[cfg(test)]
1089mod tests {
1090    use super::*;
1091
1092    #[test]
1093    fn test_log_level_ordering() {
1094        assert!(LogLevel::Trace < LogLevel::Debug);
1095        assert!(LogLevel::Debug < LogLevel::Info);
1096        assert!(LogLevel::Info < LogLevel::Warn);
1097        assert!(LogLevel::Warn < LogLevel::Error);
1098        assert!(LogLevel::Error < LogLevel::Fatal);
1099    }
1100
1101    #[test]
1102    fn test_log_level_from_str() {
1103        assert_eq!(LogLevel::parse("trace"), Some(LogLevel::Trace));
1104        assert_eq!(LogLevel::parse("DEBUG"), Some(LogLevel::Debug));
1105        assert_eq!(LogLevel::parse("Info"), Some(LogLevel::Info));
1106        assert_eq!(LogLevel::parse("WARNING"), Some(LogLevel::Warn));
1107        assert_eq!(LogLevel::parse("error"), Some(LogLevel::Error));
1108        assert_eq!(LogLevel::parse("FATAL"), Some(LogLevel::Fatal));
1109        assert_eq!(LogLevel::parse("CRITICAL"), Some(LogLevel::Fatal));
1110        assert_eq!(LogLevel::parse("invalid"), None);
1111    }
1112
1113    #[test]
1114    fn test_log_config_builder() {
1115        let config = LogConfig::builder()
1116            .level(LogLevel::Debug)
1117            .output(LogOutput::Json)
1118            .with_trace_correlation(true)
1119            .with_timestamps(true)
1120            .with_location(true)
1121            .service_name("test-service")
1122            .environment("test")
1123            .module_level("ringkernel::k2k", LogLevel::Trace)
1124            .global_field("version", "1.0.0")
1125            .build();
1126
1127        assert_eq!(config.level, LogLevel::Debug);
1128        assert_eq!(config.output, LogOutput::Json);
1129        assert!(config.trace_correlation);
1130        assert!(config.include_timestamps);
1131        assert!(config.include_location);
1132        assert_eq!(config.service_name, "test-service");
1133        assert_eq!(config.environment, "test");
1134        assert_eq!(
1135            config.effective_level("ringkernel::k2k::broker"),
1136            LogLevel::Trace
1137        );
1138    }
1139
1140    #[test]
1141    fn test_log_config_effective_level() {
1142        let config = LogConfig::builder()
1143            .level(LogLevel::Info)
1144            .module_level("ringkernel", LogLevel::Debug)
1145            .module_level("ringkernel::k2k", LogLevel::Trace)
1146            .build();
1147
1148        assert_eq!(config.effective_level("other::module"), LogLevel::Info);
1149        assert_eq!(config.effective_level("ringkernel::core"), LogLevel::Debug);
1150        assert_eq!(config.effective_level("ringkernel::k2k"), LogLevel::Trace);
1151        assert_eq!(
1152            config.effective_level("ringkernel::k2k::broker"),
1153            LogLevel::Trace
1154        );
1155    }
1156
1157    #[test]
1158    fn test_trace_context() {
1159        let ctx = TraceContext::with_new_trace()
1160            .with_field("user_id", "123")
1161            .with_field("request_id", "abc");
1162
1163        assert!(ctx.trace_id.is_some());
1164        assert!(ctx.span_id.is_some());
1165        assert_eq!(ctx.fields.get("user_id"), Some(&"123".to_string()));
1166    }
1167
1168    #[test]
1169    fn test_log_entry_json() {
1170        let config = LogConfig::builder()
1171            .service_name("test")
1172            .environment("dev")
1173            .with_timestamps(false)
1174            .with_trace_correlation(false)
1175            .build();
1176
1177        let entry = LogEntry::new(LogLevel::Info, "Test message").with_field("key", "value");
1178
1179        let json = entry.to_json(&config);
1180        assert!(json.contains(r#""level":"INFO""#));
1181        assert!(json.contains(r#""message":"Test message""#));
1182        assert!(json.contains(r#""service":"test""#));
1183    }
1184
1185    #[test]
1186    fn test_log_entry_text() {
1187        let config = LogConfig::builder()
1188            .with_timestamps(false)
1189            .with_trace_correlation(false)
1190            .build();
1191
1192        let entry = LogEntry::new(LogLevel::Warn, "Warning!").with_target("test::module");
1193
1194        let text = entry.to_text(&config);
1195        assert!(text.contains("WARN"));
1196        assert!(text.contains("[test::module]"));
1197        assert!(text.contains("Warning!"));
1198    }
1199
1200    #[test]
1201    fn test_structured_logger() {
1202        let logger = StructuredLogger::new(LogConfig::builder().level(LogLevel::Debug).build());
1203
1204        let sink = Arc::new(MemoryLogSink::new(100));
1205        logger.add_sink(sink.clone());
1206
1207        logger.info("Test message", &[("key", "value")]);
1208        logger.debug("Debug message", &[]);
1209        logger.trace("Trace message", &[]); // Should be filtered
1210
1211        assert_eq!(sink.len(), 2);
1212    }
1213
1214    #[test]
1215    fn test_memory_sink_capacity() {
1216        let sink = MemoryLogSink::new(3);
1217        let entry = LogEntry::new(LogLevel::Info, "msg");
1218
1219        sink.write(&entry, "log1").unwrap();
1220        sink.write(&entry, "log2").unwrap();
1221        sink.write(&entry, "log3").unwrap();
1222        sink.write(&entry, "log4").unwrap();
1223
1224        let logs = sink.logs();
1225        assert_eq!(logs.len(), 3);
1226        assert_eq!(logs[0], "log2");
1227        assert_eq!(logs[2], "log4");
1228    }
1229
1230    #[test]
1231    fn test_logger_stats() {
1232        let logger = StructuredLogger::new(LogConfig::default());
1233        let sink = Arc::new(MemoryLogSink::new(100));
1234        logger.add_sink(sink);
1235
1236        logger.info("info", &[]);
1237        logger.error("error", &[]);
1238        logger.warn("warn", &[]);
1239
1240        let stats = logger.stats();
1241        assert_eq!(stats.log_count, 3);
1242        assert_eq!(stats.error_count, 1);
1243        assert_eq!(stats.sink_count, 1);
1244    }
1245
1246    #[test]
1247    fn test_logger_disable() {
1248        let logger = StructuredLogger::new(LogConfig::default());
1249        let sink = Arc::new(MemoryLogSink::new(100));
1250        logger.add_sink(sink.clone());
1251
1252        logger.info("before", &[]);
1253        logger.set_enabled(false);
1254        logger.info("during", &[]);
1255        logger.set_enabled(true);
1256        logger.info("after", &[]);
1257
1258        assert_eq!(sink.len(), 2);
1259    }
1260
1261    #[test]
1262    fn test_log_value_display() {
1263        assert_eq!(LogValue::String("test".to_string()).to_string(), "test");
1264        assert_eq!(LogValue::Int(-42).to_string(), "-42");
1265        assert_eq!(LogValue::Uint(42).to_string(), "42");
1266        assert_eq!(LogValue::Bool(true).to_string(), "true");
1267    }
1268
1269    #[test]
1270    fn test_console_sink() {
1271        let sink = ConsoleSink::stderr();
1272        assert_eq!(sink.name(), "console:stderr");
1273
1274        let sink = ConsoleSink::stdout();
1275        assert_eq!(sink.name(), "console:stdout");
1276    }
1277
1278    #[test]
1279    fn test_log_config_presets() {
1280        let dev = LogConfig::development();
1281        assert_eq!(dev.level, LogLevel::Debug);
1282        assert_eq!(dev.output, LogOutput::Pretty);
1283        assert!(dev.include_location);
1284
1285        let prod = LogConfig::production();
1286        assert_eq!(prod.level, LogLevel::Info);
1287        assert_eq!(prod.output, LogOutput::Json);
1288        assert!(prod.include_thread_id);
1289    }
1290}