pub mod compliance;
pub mod entry;
pub mod formats;
pub mod persistence;
pub mod encryption;
pub mod redaction;
pub mod metrics;
pub use compliance::{ComplianceFramework, ComplianceReport, ComplianceReporter};
pub use entry::{LogEntry, SecurityLevel};
pub use formats::{CEFFormatter, LEEFFormatter, SplunkFormatter, SyslogFormatter};
pub use persistence::{LogWriter, PersistenceConfig};
pub use encryption::{LogEncryptor, EncryptedLogEntry};
pub use redaction::{LogRedactor, RedactionPattern, RedactionConfig};
pub use metrics::{LogMetrics, MetricsSnapshot};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LoggerError {
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Encryption error: {0}")]
EncryptionError(String),
#[error("Compression error: {0}")]
CompressionError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[derive(Debug, Clone)]
pub struct LoggerConfig {
pub enable_encryption: bool,
pub enable_compression: bool,
pub enable_redaction: bool,
pub correlation_id: Option<String>,
pub rate_limit_per_second: Option<u32>,
pub max_entry_size: usize,
pub retention_days: u32,
pub hash_algorithm: HashAlgorithm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashAlgorithm {
Sha256,
Sha3_256,
}
impl Default for LoggerConfig {
fn default() -> Self {
Self {
enable_encryption: false,
enable_compression: false,
enable_redaction: true,
correlation_id: None,
rate_limit_per_second: None,
max_entry_size: 1024 * 1024, retention_days: 90,
hash_algorithm: HashAlgorithm::Sha256,
}
}
}
impl LoggerConfig {
pub fn with_encryption(mut self, enable: bool) -> Self {
self.enable_encryption = enable;
self
}
pub fn with_compression(mut self, enable: bool) -> Self {
self.enable_compression = enable;
self
}
pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
self.correlation_id = Some(id.into());
self
}
pub fn with_rate_limit(mut self, limit: u32) -> Self {
self.rate_limit_per_second = Some(limit);
self
}
pub fn with_hash_algorithm(mut self, algorithm: HashAlgorithm) -> Self {
self.hash_algorithm = algorithm;
self
}
pub fn with_redaction(mut self, enable: bool) -> Self {
self.enable_redaction = enable;
self
}
}
#[derive(Debug)]
struct RateLimiter {
limit: u32,
window_start: Instant,
count: u32,
}
impl RateLimiter {
fn new(limit: u32) -> Self {
Self {
limit,
window_start: Instant::now(),
count: 0,
}
}
fn check(&mut self) -> bool {
let now = Instant::now();
if now.duration_since(self.window_start) >= Duration::from_secs(1) {
self.window_start = now;
self.count = 0;
}
if self.count >= self.limit {
false
} else {
self.count += 1;
true
}
}
}
#[derive(Clone)]
pub struct SecureLogger {
entries: Arc<Mutex<Vec<LogEntry>>>,
source: Option<String>,
config: LoggerConfig,
rate_limiter: Arc<Mutex<Option<RateLimiter>>>,
metrics: Arc<Mutex<LogMetrics>>,
redactor: Arc<LogRedactor>,
}
impl SecureLogger {
pub fn new() -> Self {
Self {
entries: Arc::new(Mutex::new(Vec::new())),
source: None,
config: LoggerConfig::default(),
rate_limiter: Arc::new(Mutex::new(None)),
metrics: Arc::new(Mutex::new(LogMetrics::new())),
redactor: Arc::new(LogRedactor::default()),
}
}
pub fn with_config(config: LoggerConfig) -> Self {
let rate_limiter = config.rate_limit_per_second.map(RateLimiter::new);
Self {
entries: Arc::new(Mutex::new(Vec::new())),
source: None,
config,
rate_limiter: Arc::new(Mutex::new(rate_limiter)),
metrics: Arc::new(Mutex::new(LogMetrics::new())),
redactor: Arc::new(LogRedactor::default()),
}
}
pub fn with_source(source: impl Into<String>) -> Self {
Self {
entries: Arc::new(Mutex::new(Vec::new())),
source: Some(source.into()),
config: LoggerConfig::default(),
rate_limiter: Arc::new(Mutex::new(None)),
metrics: Arc::new(Mutex::new(LogMetrics::new())),
redactor: Arc::new(LogRedactor::default()),
}
}
pub fn config(&self) -> &LoggerConfig {
&self.config
}
pub fn get_metrics(&self) -> MetricsSnapshot {
let metrics = self.metrics.lock().unwrap();
metrics.snapshot()
}
fn check_rate_limit(&self) -> bool {
let mut limiter = self.rate_limiter.lock().unwrap();
if let Some(ref mut rl) = *limiter {
rl.check()
} else {
true
}
}
fn apply_redaction(&self, message: &str) -> String {
if self.config.enable_redaction {
self.redactor.redact(message)
} else {
message.to_string()
}
}
pub fn info(&self, message: impl Into<String>) {
self.log(SecurityLevel::Info, message.into(), None, None);
}
pub fn warning(&self, message: impl Into<String>) {
self.log(SecurityLevel::Warning, message.into(), None, None);
}
pub fn security_event(&self, message: impl Into<String>, metadata: Option<serde_json::Value>) {
self.log(SecurityLevel::SecurityEvent, message.into(), metadata, None);
}
pub fn critical(&self, message: impl Into<String>, metadata: Option<serde_json::Value>) {
self.log(SecurityLevel::Critical, message.into(), metadata, None);
}
pub fn audit(&self, message: impl Into<String>, metadata: Option<serde_json::Value>) {
self.log(SecurityLevel::Audit, message.into(), metadata, None);
}
pub fn log_with_category(
&self,
level: SecurityLevel,
message: impl Into<String>,
metadata: Option<serde_json::Value>,
category: impl Into<String>,
) {
self.log(level, message.into(), metadata, Some(category.into()));
}
fn log(
&self,
level: SecurityLevel,
message: String,
metadata: Option<serde_json::Value>,
category: Option<String>,
) {
let entry =
LogEntry::new_with_context(level, message, metadata, self.source.clone(), category);
let mut entries = self.entries.lock().unwrap();
entries.push(entry);
}
pub fn get_entries(&self) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
entries.clone()
}
pub fn get_entries_by_level(&self, level: SecurityLevel) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
entries
.iter()
.filter(|e| e.level == level)
.cloned()
.collect()
}
pub fn get_entries_by_category(&self, category: &str) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
entries
.iter()
.filter(|e| e.category.as_deref() == Some(category))
.cloned()
.collect()
}
pub fn verify_all_integrity(&self) -> bool {
let entries = self.entries.lock().unwrap();
entries.iter().all(|entry| entry.verify_integrity())
}
pub fn export_json(&self) -> Result<String, serde_json::Error> {
let entries = self.entries.lock().unwrap();
serde_json::to_string_pretty(&*entries)
}
pub fn export_cef(&self) -> Vec<String> {
let entries = self.entries.lock().unwrap();
entries.iter().map(CEFFormatter::format).collect()
}
pub fn export_leef(&self) -> Vec<String> {
let entries = self.entries.lock().unwrap();
entries.iter().map(LEEFFormatter::format).collect()
}
pub fn export_syslog(&self) -> Vec<String> {
let entries = self.entries.lock().unwrap();
entries.iter().map(SyslogFormatter::format).collect()
}
pub fn export_splunk(&self) -> Vec<String> {
let entries = self.entries.lock().unwrap();
entries.iter().map(SplunkFormatter::format).collect()
}
pub fn count_by_level(&self, level: SecurityLevel) -> usize {
let entries = self.entries.lock().unwrap();
entries.iter().filter(|e| e.level == level).count()
}
pub fn get_entries_by_date_range(
&self,
start: chrono::DateTime<chrono::Utc>,
end: chrono::DateTime<chrono::Utc>,
) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
entries
.iter()
.filter(|e| e.timestamp >= start && e.timestamp <= end)
.cloned()
.collect()
}
pub fn clear(&self) {
let mut entries = self.entries.lock().unwrap();
entries.clear()
}
pub fn get_recent_entries(&self, count: usize) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
let total = entries.len();
if total <= count {
entries.clone()
} else {
entries[total - count..].to_vec()
}
}
pub fn search(&self, query: &str) -> Vec<LogEntry> {
let entries = self.entries.lock().unwrap();
entries
.iter()
.filter(|e| e.message.contains(query))
.cloned()
.collect()
}
pub fn get_statistics(&self) -> LogStatistics {
let entries = self.entries.lock().unwrap();
let total_entries = entries.len();
let mut stats = LogStatistics {
total_entries,
info_count: 0,
warning_count: 0,
security_event_count: 0,
critical_count: 0,
audit_count: 0,
};
for entry in entries.iter() {
match entry.level {
SecurityLevel::Info => stats.info_count += 1,
SecurityLevel::Warning => stats.warning_count += 1,
SecurityLevel::SecurityEvent => stats.security_event_count += 1,
SecurityLevel::Critical => stats.critical_count += 1,
SecurityLevel::Audit => stats.audit_count += 1,
}
}
stats
}
pub fn generate_sox_report(
&self,
period_start: chrono::DateTime<chrono::Utc>,
period_end: chrono::DateTime<chrono::Utc>,
) -> ComplianceReport {
let entries = self.get_entries();
ComplianceReporter::generate_sox_report(&entries, period_start, period_end)
}
pub fn generate_pci_report(
&self,
period_start: chrono::DateTime<chrono::Utc>,
period_end: chrono::DateTime<chrono::Utc>,
) -> ComplianceReport {
let entries = self.get_entries();
ComplianceReporter::generate_pci_report(&entries, period_start, period_end)
}
pub fn generate_glba_report(
&self,
period_start: chrono::DateTime<chrono::Utc>,
period_end: chrono::DateTime<chrono::Utc>,
) -> ComplianceReport {
let entries = self.get_entries();
ComplianceReporter::generate_glba_report(&entries, period_start, period_end)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LogStatistics {
pub total_entries: usize,
pub info_count: usize,
pub warning_count: usize,
pub security_event_count: usize,
pub critical_count: usize,
pub audit_count: usize,
}
impl Default for SecureLogger {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secure_logger() {
let logger = SecureLogger::new();
logger.info("Application started");
logger.warning("High memory usage detected");
logger.audit(
"User authentication successful",
Some(serde_json::json!({
"user_id": "12345",
"timestamp": "2024-11-06T00:00:00Z"
})),
);
assert_eq!(logger.get_entries().len(), 3);
assert_eq!(logger.count_by_level(SecurityLevel::Info), 1);
assert_eq!(logger.count_by_level(SecurityLevel::Warning), 1);
assert_eq!(logger.count_by_level(SecurityLevel::Audit), 1);
}
#[test]
fn test_thread_safety() {
use std::thread;
let logger = SecureLogger::new();
let mut handles = vec![];
for i in 0..10 {
let logger_clone = logger.clone();
let handle = thread::spawn(move || {
logger_clone.info(format!("Thread {} message", i));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(logger.get_entries().len(), 10);
}
#[test]
fn test_cef_export() {
let logger = SecureLogger::new();
logger.security_event(
"Failed login attempt",
Some(serde_json::json!({
"username": "admin"
})),
);
let cef_logs = logger.export_cef();
assert_eq!(cef_logs.len(), 1);
assert!(cef_logs[0].starts_with("CEF:0|"));
}
#[test]
fn test_leef_export() {
let logger = SecureLogger::new();
logger.critical("Security breach detected", None);
let leef_logs = logger.export_leef();
assert_eq!(leef_logs.len(), 1);
assert!(leef_logs[0].starts_with("LEEF:2.0|"));
}
#[test]
fn test_compliance_reporting() {
let logger = SecureLogger::new();
logger.audit(
"Transaction processed",
Some(serde_json::json!({
"amount": 1000,
"currency": "USD"
})),
);
let start = chrono::Utc::now() - chrono::Duration::hours(1);
let end = chrono::Utc::now();
let sox_report = logger.generate_sox_report(start, end);
assert_eq!(sox_report.framework, ComplianceFramework::SOX);
assert_eq!(sox_report.audit_events, 1);
}
#[test]
fn test_logger_with_source() {
let logger = SecureLogger::with_source("web-server-01");
logger.info("Server started");
let entries = logger.get_entries();
assert_eq!(entries[0].source, Some("web-server-01".to_string()));
}
#[test]
fn test_category_filtering() {
let logger = SecureLogger::new();
logger.log_with_category(
SecurityLevel::SecurityEvent,
"Login failed",
None,
"authentication",
);
logger.log_with_category(SecurityLevel::Info, "Page loaded", None, "web");
let auth_events = logger.get_entries_by_category("authentication");
assert_eq!(auth_events.len(), 1);
assert_eq!(auth_events[0].message, "Login failed");
}
}