dmsc/log/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Logging System
21//! 
22//! This module provides a comprehensive logging system for DMSC, supporting multiple output formats,
23//! log levels, and configurable logging behavior. It includes support for structured logging,
24//! distributed tracing integration, and log rotation.
25//! 
26//! ## Key Components
27//! 
28//! - **DMSCLogLevel**: Enum defining supported log levels (Debug, Info, Warn, Error)
29//! - **DMSCLogConfig**: Configuration struct for logging behavior
30//! - **DMSCLogContext**: Thread-local context for adding contextual information to logs
31//! - **DMSCLogger**: Public-facing logger class for application use
32//! - **LoggerImpl**: Internal logger implementation
33//! 
34//! ## Design Principles
35//! 
36//! 1. **Multiple Outputs**: Supports both console and file logging
37//! 2. **Structured Logging**: Supports both text and JSON formats
38//! 3. **Distributed Tracing**: Integrates with distributed tracing context
39//! 4. **Configurable**: Highly configurable through `DMSCLogConfig`
40//! 5. **Performance**: Includes sampling support for high-volume logging
41//! 6. **Log Rotation**: Supports size-based log rotation
42//! 7. **Contextual Logging**: Allows adding contextual information to logs
43//! 
44//! ## Usage
45//! 
46//! ```rust,ignore
47//! use dmsc::prelude::*;
48//! 
49//! fn example() -> DMSCResult<()> {
50//!     // Create a default log configuration
51//!     let log_config = DMSCLogConfig::default();
52//!     
53//!     // Create a file system instance (usually provided by the service context)
54//!     let fs = DMSCFileSystem::new();
55//!     
56//!     // Create a logger
57//!     let logger = DMSCLogger::new(&log_config, fs);
58//!     
59//!     // Log messages at different levels
60//!     logger.debug("example", "Debug message")?;
61//!     logger.info("example", "Info message")?;
62//!     logger.warn("example", "Warning message")?;
63//!     logger.error("example", "Error message")?;
64//!     
65//!     Ok(())
66//! }
67//! ```
68
69// Logging module for DMSC.
70// This is a first-stage implementation using std only; can be extended later.
71
72use std::fmt::Debug;
73use std::sync::{Arc, Mutex, Condvar};
74use std::thread;
75use std::time::Duration;
76use std::collections::VecDeque;
77
78use crate::core::DMSCResult;
79use crate::fs::DMSCFileSystem;
80use rand;
81use serde_json::json;
82use std::fs as stdfs;
83use std::time::SystemTime;
84use std::time::UNIX_EPOCH;
85mod context;
86pub use context::DMSCLogContext;
87
88/// Log level definition.
89/// 
90/// This enum defines the supported log levels in DMSC, ordered by severity from lowest to highest.
91#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(eq, eq_int))]
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum DMSCLogLevel {
94    /// Debug level: Detailed information for debugging purposes
95    Debug,
96    /// Info level: General information about application operation
97    Info,
98    /// Warn level: Warning messages about potential issues
99    Warn,
100    /// Error level: Error messages about failures
101    Error,
102}
103
104impl DMSCLogLevel {
105    /// Returns the string representation of the log level.
106    /// 
107    /// # Returns
108    /// 
109    /// A static string representing the log level ("DEBUG", "INFO", "WARN", or "ERROR")
110    pub fn as_str(&self) -> &'static str {
111        match self {
112            DMSCLogLevel::Debug => "DEBUG",
113            DMSCLogLevel::Info => "INFO",
114            DMSCLogLevel::Warn => "WARN",
115            DMSCLogLevel::Error => "ERROR",
116        }
117    }
118
119    /// Returns the color block emoji for the log level.
120    /// 
121    /// # Returns
122    /// 
123    /// A static string representing the color block emoji
124    pub fn color_block(&self) -> &'static str {
125        match self {
126            DMSCLogLevel::Debug => "🟦",
127            DMSCLogLevel::Info => "🟩",
128            DMSCLogLevel::Warn => "🟨",
129            DMSCLogLevel::Error => "🟥",
130        }
131    }
132
133    /// Parses a log level from an environment variable.
134    /// 
135    /// Reads the `DMSC_LOG_LEVEL` environment variable and returns the corresponding log level.
136    /// If the environment variable is not set or contains an invalid value, returns `None`.
137    /// 
138    /// # Returns
139    /// 
140    /// An `Option<DMSCLogLevel>` containing the parsed log level
141    pub fn from_env() -> Option<Self> {
142        std::env::var("DMSC_LOG_LEVEL").ok().and_then(|s| {
143            match s.to_ascii_uppercase().as_str() {
144                "DEBUG" => Some(DMSCLogLevel::Debug),
145                "INFO" => Some(DMSCLogLevel::Info),
146                "WARN" | "WARNING" => Some(DMSCLogLevel::Warn),
147                "ERROR" => Some(DMSCLogLevel::Error),
148                _ => None,
149            }
150        })
151    }
152
153    /// Creates a log level from a string.
154    /// 
155    /// # Parameters
156    /// 
157    /// - `s`: The string to parse
158    /// 
159    /// # Returns
160    /// 
161    /// An `Option<DMSCLogLevel>` containing the parsed log level
162    pub fn from_str(s: &str) -> Option<Self> {
163        match s.to_ascii_uppercase().as_str() {
164            "DEBUG" => Some(DMSCLogLevel::Debug),
165            "INFO" => Some(DMSCLogLevel::Info),
166            "WARN" | "WARNING" => Some(DMSCLogLevel::Warn),
167            "ERROR" => Some(DMSCLogLevel::Error),
168            _ => None,
169        }
170    }
171}
172
173/// Public logging configuration class.
174/// 
175/// This struct defines the configuration options for the DMSC logging system, including
176/// log level, output formats, sampling, and log rotation settings.
177#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
178#[derive(Clone)]
179pub struct DMSCLogConfig {
180    /// Minimum log level to be logged
181    pub level: DMSCLogLevel,
182    /// Whether console logging is enabled
183    pub console_enabled: bool,
184    /// Whether file logging is enabled
185    pub file_enabled: bool,
186    /// Default sampling rate (0.0 to 1.0, where 1.0 means all logs are sampled)
187    pub sampling_default: f32,
188    /// Name of the log file
189    pub file_name: String,
190    /// Whether to use JSON format for logs
191    pub json_format: bool,
192    /// When to rotate logs (currently only "size" or "none" are supported)
193    pub rotate_when: String,
194    /// Maximum file size in bytes before rotation (used when rotate_when == "size")
195    pub max_bytes: u64,
196    /// Whether to use color blocks in log output
197    pub color_blocks: bool,
198}
199
200#[cfg(feature = "pyo3")]
201#[pyo3::prelude::pymethods]
202impl DMSCLogConfig {
203    #[new]
204    fn py_new() -> Self {
205        Self::default()
206    }
207
208    #[staticmethod]
209    #[pyo3(name = "default")]
210    fn default_py() -> Self {
211        Self::default()
212    }
213
214    #[staticmethod]
215    #[pyo3(signature = (level="info", console_enabled=true, file_enabled=false, file_name="dmsc.log", json_format=false, max_bytes=10485760, color_blocks=true))]
216    fn create(
217        level: &str,
218        console_enabled: bool,
219        file_enabled: bool,
220        file_name: &str,
221        json_format: bool,
222        max_bytes: u64,
223        color_blocks: bool,
224    ) -> Self {
225        let log_level = match level.to_uppercase().as_str() {
226            "DEBUG" => DMSCLogLevel::Debug,
227            "INFO" => DMSCLogLevel::Info,
228            "WARN" | "WARNING" => DMSCLogLevel::Warn,
229            "ERROR" => DMSCLogLevel::Error,
230            _ => DMSCLogLevel::Info,
231        };
232        Self {
233            level: log_level,
234            console_enabled,
235            file_enabled,
236            sampling_default: 1.0,
237            file_name: file_name.to_string(),
238            json_format,
239            rotate_when: "size".to_string(),
240            max_bytes,
241            color_blocks,
242        }
243    }
244}
245
246impl DMSCLogConfig {
247    /// Creates a log configuration from a `DMSCConfig` instance.
248    /// 
249    /// This method reads logging configuration from a `DMSCConfig` instance, using the following keys:
250    /// - log.level: Log level (DEBUG, INFO, WARN, ERROR)
251    /// - log.console_enabled: Whether console logging is enabled
252    /// - log.file_enabled: Whether file logging is enabled
253    /// - log.sampling_default: Default sampling rate
254    /// - log.file_name: Name of the log file
255    /// - log.file_format: Log format ("json" for JSON format, anything else for text)
256    /// - log.rotate_when: When to rotate logs
257    /// - log.max_bytes: Maximum file size before rotation
258    /// 
259    /// # Parameters
260    /// 
261    /// - `config`: The `DMSCConfig` instance to read from
262    /// 
263    /// # Returns
264    /// 
265    /// A `DMSCLogConfig` instance with configuration from the given `DMSCConfig`
266    pub fn from_config(config: &crate::config::DMSCConfig) -> Self {
267        let mut base = DMSCLogConfig::default();
268
269        if let Some(level_str) = config.get_str("log.level") {
270            if let Some(level) = DMSCLogLevel::from_str(level_str) {
271                base.level = level;
272            }
273        }
274
275        if let Some(v) = config.get_f32("log.sampling_default") {
276            base.sampling_default = v.clamp(0.0, 1.0);
277        }
278
279        if let Some(file_name) = config.get_str("log.file_name") {
280            if !file_name.is_empty() {
281                base.file_name = file_name.to_string();
282            }
283        }
284
285        if let Some(fmt) = config.get_str("log.file_format") {
286            if fmt.eq_ignore_ascii_case("json") {
287                base.json_format = true;
288            }
289        }
290
291        if let Some(rotate) = config.get_str("log.rotate_when") {
292            if !rotate.is_empty() {
293                base.rotate_when = rotate.to_string();
294            }
295        }
296
297        if let Some(v) = config.get_u64("log.max_bytes") {
298            if v > 0 {
299                base.max_bytes = v;
300            }
301        }
302
303        if let Some(v) = config.get_bool("log.console_enabled") {
304            base.console_enabled = v;
305        }
306
307        if let Some(v) = config.get_bool("log.file_enabled") {
308            base.file_enabled = v;
309        }
310
311        if let Some(v) = config.get_bool("log.color_blocks") {
312            base.color_blocks = v;
313        }
314
315        base
316    }
317
318    /// Creates a log configuration from environment variables.
319    /// 
320    /// This method reads logging configuration from environment variables:
321    /// - DMSC_LOG_LEVEL: Log level (DEBUG, INFO, WARN, ERROR)
322    /// - DMSC_LOG_CONSOLE_ENABLED: Whether console logging is enabled (true/false)
323    /// - DMSC_LOG_FILE_ENABLED: Whether file logging is enabled (true/false)
324    /// - DMSC_LOG_SAMPLING_DEFAULT: Default sampling rate (0.0-1.0)
325    /// - DMSC_LOG_FILE_NAME: Name of the log file
326    /// - DMSC_LOG_FILE_FORMAT: Log format ("json" for JSON format)
327    /// - DMSC_LOG_ROTATE_WHEN: When to rotate logs
328    /// - DMSC_LOG_MAX_BYTES: Maximum file size before rotation
329    /// 
330    /// # Returns
331    /// 
332    /// A `DMSCLogConfig` instance with configuration from environment variables
333    pub fn from_env() -> Self {
334        let mut base = DMSCLogConfig::default();
335
336        if let Some(level) = DMSCLogLevel::from_env() {
337            base.level = level;
338        }
339
340        if let Ok(v) = std::env::var("DMSC_LOG_SAMPLING_DEFAULT") {
341            if let Ok(rate) = v.parse::<f32>() {
342                base.sampling_default = rate.clamp(0.0, 1.0);
343            }
344        }
345
346        if let Ok(file_name) = std::env::var("DMSC_LOG_FILE_NAME") {
347            if !file_name.is_empty() {
348                base.file_name = file_name;
349            }
350        }
351
352        if let Ok(fmt) = std::env::var("DMSC_LOG_FILE_FORMAT") {
353            if fmt.eq_ignore_ascii_case("json") {
354                base.json_format = true;
355            }
356        }
357
358        if let Ok(rotate) = std::env::var("DMSC_LOG_ROTATE_WHEN") {
359            if !rotate.is_empty() {
360                base.rotate_when = rotate;
361            }
362        }
363
364        if let Ok(v) = std::env::var("DMSC_LOG_MAX_BYTES") {
365            if let Ok(bytes) = v.parse::<u64>() {
366                if bytes > 0 {
367                    base.max_bytes = bytes;
368                }
369            }
370        }
371
372        if let Ok(v) = std::env::var("DMSC_LOG_CONSOLE_ENABLED") {
373            if v.eq_ignore_ascii_case("true") || v == "1" {
374                base.console_enabled = true;
375            } else if v.eq_ignore_ascii_case("false") || v == "0" {
376                base.console_enabled = false;
377            }
378        }
379
380        if let Ok(v) = std::env::var("DMSC_LOG_FILE_ENABLED") {
381            if v.eq_ignore_ascii_case("true") || v == "1" {
382                base.file_enabled = true;
383            } else if v.eq_ignore_ascii_case("false") || v == "0" {
384                base.file_enabled = false;
385            }
386        }
387
388        if let Ok(v) = std::env::var("DMSC_LOG_COLOR_BLOCKS") {
389            if v.eq_ignore_ascii_case("true") || v == "1" {
390                base.color_blocks = true;
391            } else if v.eq_ignore_ascii_case("false") || v == "0" {
392                base.color_blocks = false;
393            }
394        }
395
396        base
397    }
398}
399
400/// Default implementation for DMSCLogConfig
401impl Default for DMSCLogConfig {
402    fn default() -> Self {
403        DMSCLogConfig {
404            level: DMSCLogLevel::Info,
405            console_enabled: true,
406            file_enabled: true,
407            sampling_default: 1.0,
408            file_name: "dms.log".to_string(),
409            json_format: false,
410            rotate_when: "size".to_string(),
411            max_bytes: 10 * 1024 * 1024,
412            color_blocks: true,
413        }
414    }
415}
416
417/// Log entry for caching
418struct LogEntry {
419    level: DMSCLogLevel,
420    target: String,
421    message: String,
422    timestamp: String,
423    context: serde_json::Map<String, serde_json::Value>,
424}
425
426/// Internal logger implementation.
427/// 
428/// This struct contains the internal implementation of the logging system, including
429/// log level checking, sampling, log message formatting, and caching.
430#[derive(Clone)]
431struct LoggerImpl {
432    /// Minimum log level to be logged
433    level: DMSCLogLevel,
434    /// File system instance for writing log files
435    #[allow(dead_code)]
436    fs: DMSCFileSystem,
437    /// Default sampling rate
438    sampling_default: f32,
439    /// Whether console logging is enabled
440    #[allow(dead_code)]
441    console_enabled: bool,
442    /// Whether file logging is enabled
443    #[allow(dead_code)]
444    file_enabled: bool,
445    /// Name of the log file
446    #[allow(dead_code)]
447    file_name: String,
448    /// Whether to use JSON format for logs
449    #[allow(dead_code)]
450    json_format: bool,
451    /// When to rotate logs (currently only "size" or "none" are supported)
452    #[allow(dead_code)]
453    rotate_when: String,
454    /// Maximum file size in bytes before rotation (used when rotate_when == "size")
455    #[allow(dead_code)]
456    max_bytes: u64,
457    /// Whether to use color blocks in log output
458    #[allow(dead_code)]
459    color_blocks: bool,
460    /// Log cache for batch writing
461    log_cache: Arc<(Mutex<VecDeque<LogEntry>>, Condvar)>,
462    /// Cache size limit
463    cache_size_limit: usize,
464    /// Flush interval in milliseconds
465    #[allow(dead_code)]
466    flush_interval_ms: u64,
467    /// Shutdown flag
468    #[allow(dead_code)]
469    shutdown_flag: Arc<Mutex<bool>>,
470}
471
472impl LoggerImpl {
473    /// Creates a new internal logger implementation.
474    /// 
475    /// # Parameters
476    /// 
477    /// - `config`: The `DMSCLogConfig` instance to use for configuration
478    /// - `fs`: The `DMSCFileSystem` instance to use for writing log files
479    /// 
480    /// # Returns
481    /// 
482    /// A new `LoggerImpl` instance
483    fn new(config: &DMSCLogConfig, fs: DMSCFileSystem) -> Self {
484        let log_cache = Arc::new((Mutex::new(VecDeque::new()), Condvar::new()));
485        let shutdown_flag = Arc::new(Mutex::new(false));
486        let cache_size_limit = 1000;
487        let flush_interval_ms = 500;
488        
489        // Create a copy of the necessary fields for the background thread
490        let bg_log_cache = Arc::clone(&log_cache);
491        let bg_fs = fs.clone();
492        let bg_file_name = config.file_name.clone();
493        let bg_json_format = config.json_format;
494        let bg_rotate_when = config.rotate_when.clone();
495        let bg_max_bytes = config.max_bytes;
496        let bg_console_enabled = config.console_enabled;
497        let bg_color_blocks = config.color_blocks;
498        let bg_shutdown_flag = Arc::clone(&shutdown_flag);
499        
500        // Start background flush thread
501        thread::spawn(move || {
502            let mut last_flush = SystemTime::now();
503            
504            loop {
505                // Check if we should shutdown
506                let shutdown_flag = bg_shutdown_flag.lock().expect("Log shutdown flag lock poisoned");
507                if *shutdown_flag {
508                    // Flush remaining logs before shutting down
509                    Self::flush_cache(
510                        &bg_log_cache,
511                        &bg_fs,
512                        &bg_file_name,
513                        bg_json_format,
514                        &bg_rotate_when,
515                        bg_max_bytes,
516                        bg_console_enabled,
517                        bg_color_blocks
518                    ).unwrap_or(());
519                    break;
520                }
521                drop(shutdown_flag);
522                
523                // Check if we need to flush based on time or cache size
524                let now = SystemTime::now();
525                let time_since_last_flush = now.duration_since(last_flush).unwrap_or(Duration::from_millis(0));
526                
527                let cache_len = bg_log_cache.0.lock().expect("Log cache lock poisoned").len();
528                
529                if time_since_last_flush >= Duration::from_millis(flush_interval_ms) || cache_len >= cache_size_limit {
530                    Self::flush_cache(
531                        &bg_log_cache,
532                        &bg_fs,
533                        &bg_file_name,
534                        bg_json_format,
535                        &bg_rotate_when,
536                        bg_max_bytes,
537                        bg_console_enabled,
538                        bg_color_blocks
539                    ).unwrap_or(());
540                    last_flush = now;
541                }
542                
543                // Wait for a short time or until signaled
544                let (lock, cvar) = &*bg_log_cache;
545                let _ = cvar.wait_timeout(lock.lock().unwrap(), Duration::from_millis(100)).unwrap();
546            }
547        });
548        
549        LoggerImpl {
550            level: config.level,
551            fs,
552            sampling_default: config.sampling_default,
553            console_enabled: config.console_enabled,
554            file_enabled: config.file_enabled,
555            file_name: config.file_name.clone(),
556            json_format: config.json_format,
557            rotate_when: config.rotate_when.clone(),
558            max_bytes: config.max_bytes,
559            color_blocks: config.color_blocks,
560            log_cache,
561            cache_size_limit,
562            flush_interval_ms,
563            shutdown_flag,
564        }
565    }
566    
567    /// Flushes the log cache to disk and console
568    /// 
569    /// # Parameters
570    /// 
571    /// - `log_cache`: The log cache to flush
572    /// - `fs`: The file system instance to use for writing log files
573    /// - `file_name`: The name of the log file
574    /// - `json_format`: Whether to use JSON format for logs
575    /// - `rotate_when`: When to rotate logs
576    /// - `max_bytes`: Maximum file size before rotation
577    /// - `console_enabled`: Whether console logging is enabled
578    /// - `color_blocks`: Whether to use color blocks in log output
579    /// 
580    /// # Returns
581    /// 
582    /// A `DMSCResult` indicating success or failure
583    fn flush_cache(
584        log_cache: &Arc<(Mutex<VecDeque<LogEntry>>, Condvar)>,
585        fs: &DMSCFileSystem,
586        file_name: &str,
587        json_format: bool,
588        rotate_when: &str,
589        max_bytes: u64,
590        console_enabled: bool,
591        color_blocks: bool
592    ) -> DMSCResult<()> {
593        let (lock, _cvar) = &**log_cache;
594        let mut cache = lock.lock().unwrap();
595        
596        if cache.is_empty() {
597            return Ok(());
598        }
599        
600        // Collect all logs to flush
601        let logs_to_flush: Vec<LogEntry> = cache.drain(..).collect();
602        drop(cache);
603        
604        // Process logs in batch
605        let mut file_logs = Vec::new();
606        let mut console_logs = Vec::new();
607        
608        for log_entry in logs_to_flush {
609            // Format log entry
610            let line = if json_format {
611                // Ensure all required fields are present in JSON format
612                let mut log_obj = log_entry.context.clone();
613                
614                // Add any missing standard fields
615                if !log_obj.contains_key("level") {
616                    log_obj.insert("level".to_string(), serde_json::Value::String(log_entry.level.as_str().to_string()));
617                }
618                if !log_obj.contains_key("target") {
619                    log_obj.insert("target".to_string(), serde_json::Value::String(log_entry.target.clone()));
620                }
621                if !log_obj.contains_key("message") {
622                    log_obj.insert("message".to_string(), serde_json::Value::String(log_entry.message.clone()));
623                }
624                if !log_obj.contains_key("timestamp") {
625                    log_obj.insert("timestamp".to_string(), serde_json::Value::String(log_entry.timestamp.clone()));
626                }
627                
628                serde_json::to_string(&log_obj)?
629            } else {
630                // Extract context fields for text format
631                let ctx_kv: Vec<(String, String)> = log_entry.context.iter()
632                    .filter(|(k, _)| *k != "timestamp" && *k != "level" && *k != "target" && *k != "message" && *k != "trace_id" && *k != "span_id")
633                    .map(|(k, v)| (k.clone(), v.to_string()))
634                    .collect();
635                
636                // Extract trace and span IDs if present
637                let trace_info = match (log_entry.context.get("trace_id"), log_entry.context.get("span_id")) {
638                    (Some(trace), Some(span)) => format!(" trace_id={trace} span_id={span}"),
639                    (Some(trace), None) => format!(" trace_id={trace}"),
640                    (None, Some(span)) => format!(" span_id={span}"),
641                    (None, None) => String::new(),
642                };
643                
644                // Extract event name from context or use target as fallback
645                let event = log_entry.context.get("event")
646                    .and_then(|v| v.as_str())
647                    .unwrap_or(&log_entry.target);
648                
649                // Format context fields for display
650                let ctx_display = if ctx_kv.is_empty() {
651                    String::new()
652                } else {
653                    let parts: Vec<String> = ctx_kv
654                        .iter()
655                        .map(|(k, v)| format!("{}={}", k, v))
656                        .collect();
657                    format!(" | {}", parts.join(", "))
658                };
659                
660                // Format trace info for display
661                let trace_display = if trace_info.is_empty() {
662                    String::new()
663                } else {
664                    format!(" | {}", trace_info.trim())
665                };
666                
667                // New log format with optional color block and | separators
668                let color_block = if color_blocks {
669                    log_entry.level.color_block()
670                } else {
671                    ""
672                };
673                let color_sep = if color_blocks { " | " } else { "" };
674                format!(
675                    "{}{}{} | {:5} | {} | event={}{} | {}{}",
676                    color_block,
677                    color_sep,
678                    log_entry.timestamp,
679                    log_entry.level.as_str(),
680                    log_entry.target,
681                    event,
682                    trace_display,
683                    log_entry.message,
684                    ctx_display,
685                )
686            };
687            
688            // Separate console and file logs
689            if console_enabled {
690                console_logs.push(line.clone());
691            }
692            
693            if !line.is_empty() {
694                file_logs.push(line);
695            }
696        }
697        
698        // Write to console in batch
699        if !console_logs.is_empty() {
700            for line in console_logs {
701                log::info!("{line}");
702            }
703        }
704        
705        // Write to file in batch
706        if !file_logs.is_empty() {
707            let log_file = fs.logs_dir().join(file_name);
708            
709            // Simple size-based rotation if enabled
710            if rotate_when.eq_ignore_ascii_case("size") && max_bytes > 0 {
711                if let Ok(meta) = stdfs::metadata(&log_file) {
712                    if meta.len() >= max_bytes {
713                        if let Some(parent) = log_file.parent() {
714                            let base = log_file.file_name().and_then(|s| s.to_str()).unwrap_or("dms.log");
715                            let ts = SystemTime::now()
716                                .duration_since(UNIX_EPOCH)
717                                .map_err(|e| crate::core::DMSCError::Other(format!("timestamp error: {e}")))?;
718                            let rotated = parent.join(format!("{}.{}", base, ts.as_millis()));
719                            let _ = stdfs::rename(&log_file, &rotated);
720                        }
721                    }
722                }
723            }
724            
725            // Batch write to file
726            let content = file_logs.join("\n") + "\n";
727            fs.append_text(&log_file, &content)?;
728        }
729        
730        Ok(())
731    }
732
733    /// Determines if a message with the given level should be logged.
734    /// 
735    /// # Parameters
736    /// 
737    /// - `level`: The log level of the message
738    /// 
739    /// # Returns
740    /// 
741    /// `true` if the message should be logged, `false` otherwise
742    fn should_log(&self, level: DMSCLogLevel) -> bool {
743        (level as u8) >= (self.level as u8)
744    }
745
746    /// Determines if an event should be logged based on sampling.
747    /// 
748    /// # Parameters
749    /// 
750    /// - `_event`: The event name (currently unused, reserved for future per-event sampling)
751    /// 
752    /// # Returns
753    /// 
754    /// `true` if the event should be logged, `false` otherwise
755    fn should_log_event(&self, event: &str) -> bool {
756        // Advanced event-based sampling with per-event configuration support
757        
758        // First check if we have specific sampling rules for this event
759        if let Some(event_sampling_rate) = self.get_event_sampling_rate(event) {
760            if event_sampling_rate >= 1.0 {
761                return true;
762            } else if event_sampling_rate <= 0.0 {
763                return false;
764            } else {
765                let r = rand::random::<f32>();
766                return r < event_sampling_rate;
767            }
768        }
769        
770        // Fall back to default sampling rate
771        if self.sampling_default >= 1.0 {
772            true
773        } else if self.sampling_default <= 0.0 {
774            false
775        } else {
776            let r = rand::random::<f32>();
777            r < self.sampling_default
778        }
779    }
780    
781    /// Get the sampling rate for a specific event type
782    /// 
783    /// # Parameters
784    /// 
785    /// - `event`: The event name to get sampling rate for
786    /// 
787    /// # Returns
788    /// 
789    /// Optional sampling rate (0.0 to 1.0) if specific rate is configured
790    fn get_event_sampling_rate(&self, event: &str) -> Option<f32> {
791        // In a production environment, this would:
792        // 1. Load per-event sampling configuration from config files
793        // 2. Support dynamic configuration updates
794        // 3. Handle event patterns and categories
795        // 4. Support A/B testing for different sampling rates
796        
797        // For now, we support a few common event types with different sampling rates
798        match event {
799            "database_query" => Some(0.1),      // Sample 10% of database queries
800            "api_request" => Some(0.5),        // Sample 50% of API requests
801            "cache_hit" => Some(0.05),         // Sample 5% of cache hits
802            "cache_miss" => Some(1.0),         // Log all cache misses
803            "error" => Some(1.0),             // Log all errors
804            "warning" => Some(0.8),           // Log 80% of warnings
805            _ => None,                         // Use default for unknown events
806        }
807    }
808
809    /// Returns the current timestamp in ISO 8601 format.
810    /// 
811    /// # Returns
812    /// 
813    /// A string representing the current timestamp in ISO 8601 format (e.g., "1630000000.123Z")
814    fn now_timestamp() -> String {
815        match SystemTime::now().duration_since(UNIX_EPOCH) {
816            Ok(dur) => {
817                let secs = dur.as_secs();
818                let millis = dur.subsec_millis();
819                format!("{secs}.{millis:03}Z")
820            }
821            Err(_) => "0.000Z".to_string(),
822        }
823    }
824
825    /// Logs a message with the given level, target, and message.
826    /// 
827    /// This method handles the complete logging process, including:
828    /// 1. Checking if the message should be logged based on level
829    /// 2. Sampling the message if applicable
830    /// 3. Formatting the message (text or JSON)
831    /// 4. Adding contextual information and distributed tracing fields
832    /// 5. Adding the log entry to the cache for batch processing
833    /// 
834    /// # Parameters
835    /// 
836    /// - `level`: The log level of the message
837    /// - `target`: The target of the log message (usually a module or component name)
838    /// - `message`: The message to log (must implement `Debug`)
839    /// 
840    /// # Returns
841    /// 
842    /// A `DMSCResult` indicating success or failure
843    fn log_message<T: Debug>(&self, level: DMSCLogLevel, target: &str, message: T) -> DMSCResult<()> {
844        if !self.should_log(level) {
845            return Ok(());
846        }
847
848        let event = target; // simple default; can be extended to accept explicit event names.
849        if !self.should_log_event(event) {
850            return Ok(());
851        }
852
853        let ts = Self::now_timestamp();
854        let message_str = format!("{message:?}");
855        let ctx_kv = DMSCLogContext::get_all();
856
857        // Create log entry with structured data
858        let mut log_entry_context = serde_json::Map::new();
859        log_entry_context.insert("timestamp".to_string(), json!(ts));
860        log_entry_context.insert("level".to_string(), json!(level.as_str()));
861        log_entry_context.insert("target".to_string(), json!(target));
862        log_entry_context.insert("event".to_string(), json!(event));
863        log_entry_context.insert("message".to_string(), json!(message_str));
864        
865        // Add distributed tracing fields if present
866        if let Some(trace_id) = DMSCLogContext::get_trace_id() {
867            log_entry_context.insert("trace_id".to_string(), json!(trace_id));
868        }
869        if let Some(span_id) = DMSCLogContext::get_span_id() {
870            log_entry_context.insert("span_id".to_string(), json!(span_id));
871        }
872        if let Some(parent_span_id) = DMSCLogContext::get_parent_span_id() {
873            log_entry_context.insert("parent_span_id".to_string(), json!(parent_span_id));
874        }
875        
876        // Add context fields
877        if !ctx_kv.is_empty() {
878            for (k, v) in ctx_kv.iter() {
879                log_entry_context.insert(k.clone(), json!(v));
880            }
881        }
882
883        // Create log entry for caching
884        let log_entry = LogEntry {
885            level,
886            target: target.to_string(),
887            message: message_str,
888            timestamp: ts,
889            context: log_entry_context,
890        };
891
892        // Add log entry to cache
893        let (lock, cvar) = &*self.log_cache;
894        let mut cache = lock.lock().unwrap();
895        cache.push_back(log_entry);
896        
897        // Signal the background thread if cache is full
898        if cache.len() >= self.cache_size_limit {
899            cvar.notify_one();
900        }
901        
902        Ok(())
903    }
904}
905
906/// Public-facing logger class.
907/// 
908/// This struct provides the public API for logging in DMSC, wrapping the internal `LoggerImpl`.
909#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
910#[derive(Clone)]
911pub struct DMSCLogger {
912    /// Internal logger implementation
913    inner: LoggerImpl,
914}
915
916impl DMSCLogger {
917    /// Creates a new logger instance.
918    /// 
919    /// # Parameters
920    /// 
921    /// - `config`: The `DMSCLogConfig` instance to use for configuration
922    /// - `fs`: The `DMSCFileSystem` instance to use for writing log files
923    /// 
924    /// # Returns
925    /// 
926    /// A new `DMSCLogger` instance
927    pub fn new(config: &DMSCLogConfig, fs: DMSCFileSystem) -> Self {
928        let inner = LoggerImpl::new(config, fs);
929        DMSCLogger { inner }
930    }
931
932    /// Logs a debug message.
933    /// 
934    /// # Parameters
935    /// 
936    /// - `target`: The target of the log message (usually a module or component name)
937    /// - `message`: The message to log (must implement `Debug`)
938    /// 
939    /// # Returns
940    /// 
941    /// A `DMSCResult` indicating success or failure
942    pub fn debug<T: Debug>(&self, target: &str, message: T) -> DMSCResult<()> {
943        self.inner.log_message(DMSCLogLevel::Debug, target, message)
944    }
945
946    /// Logs an info message.
947    /// 
948    /// # Parameters
949    /// 
950    /// - `target`: The target of the log message (usually a module or component name)
951    /// - `message`: The message to log (must implement `Debug`)
952    /// 
953    /// # Returns
954    /// 
955    /// A `DMSCResult` indicating success or failure
956    pub fn info<T: Debug>(&self, target: &str, message: T) -> DMSCResult<()> {
957        self.inner.log_message(DMSCLogLevel::Info, target, message)
958    }
959
960    /// Logs a warning message.
961    /// 
962    /// # Parameters
963    /// 
964    /// - `target`: The target of the log message (usually a module or component name)
965    /// - `message`: The message to log (must implement `Debug`)
966    /// 
967    /// # Returns
968    /// 
969    /// A `DMSCResult` indicating success or failure
970    pub fn warn<T: Debug>(&self, target: &str, message: T) -> DMSCResult<()> {
971        self.inner.log_message(DMSCLogLevel::Warn, target, message)
972    }
973
974    /// Logs an error message.
975    /// 
976    /// # Parameters
977    /// 
978    /// - `target`: The target of the log message (usually a module or component name)
979    /// - `message`: The message to log (must implement `Debug`)
980    /// 
981    /// # Returns
982    /// 
983    /// A `DMSCResult` indicating success or failure
984    pub fn error<T: Debug>(&self, target: &str, message: T) -> DMSCResult<()> {
985        self.inner.log_message(DMSCLogLevel::Error, target, message)
986    }
987}
988
989#[cfg(feature = "pyo3")]
990#[pyo3::prelude::pymethods]
991impl DMSCLogger {
992    #[new]
993    fn py_new(config: DMSCLogConfig, fs: DMSCFileSystem) -> Self {
994        Self::new(&config, fs)
995    }
996
997    #[pyo3(name = "debug")]
998    fn py_debug(&self, target: &str, message: &str) -> pyo3::PyResult<()> {
999        self.inner
1000            .log_message(DMSCLogLevel::Debug, target, message)
1001            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
1002    }
1003
1004    #[pyo3(name = "info")]
1005    fn py_info(&self, target: &str, message: &str) -> pyo3::PyResult<()> {
1006        self.inner
1007            .log_message(DMSCLogLevel::Info, target, message)
1008            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
1009    }
1010
1011    #[pyo3(name = "warn")]
1012    fn py_warn(&self, target: &str, message: &str) -> pyo3::PyResult<()> {
1013        self.inner
1014            .log_message(DMSCLogLevel::Warn, target, message)
1015            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
1016    }
1017
1018    #[pyo3(name = "error")]
1019    fn py_error(&self, target: &str, message: &str) -> pyo3::PyResult<()> {
1020        self.inner
1021            .log_message(DMSCLogLevel::Error, target, message)
1022            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
1023    }
1024}