Skip to main content

aura_effects/system/
logging.rs

1//! Layer 3: System Logging Effect Handler - Production Only
2//!
3//! Stateless single-party implementation of system logging from aura-core (Layer 1).
4//! This handler provides production logging operations delegating to external logging services.
5//!
6//! **Layer Constraint**: NO stateful patterns or multi-party coordination.
7//! This module contains only production-grade stateless handlers.
8// System handlers are stateless in Layer 3.
9
10use async_trait::async_trait;
11use aura_core::effects::{SystemEffects, SystemError};
12use aura_core::types::identifiers::DeviceId;
13use aura_core::SessionId;
14use serde_json::Value;
15use std::collections::HashMap;
16use tracing::{debug, error, info, warn};
17use uuid::Uuid;
18
19use super::types::{AuditAction, ComponentId, LogLevel};
20
21/// Log entry with structured metadata
22#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23pub struct LogEntry {
24    /// Log level (debug, info, warn, error)
25    pub level: LogLevel,
26    /// Log message content
27    pub message: String,
28    /// Component that generated the log
29    pub component: ComponentId,
30    /// Associated session identifier
31    pub session_id: Option<SessionId>,
32    /// Associated device identifier
33    pub device_id: Option<DeviceId>,
34    /// Structured metadata key-value pairs
35    pub metadata: HashMap<String, Value>,
36    /// Unique trace identifier for request correlation
37    pub trace_id: Option<Uuid>,
38}
39
40/// Audit log entry for security-critical events
41#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
42pub struct AuditEntry {
43    /// Type of event (e.g., "authentication", "authorization", "key_operation")
44    pub event_type: String,
45    /// Actor performing the action
46    pub actor: Option<DeviceId>,
47    /// Resource being acted upon
48    pub resource: String,
49    /// Action performed (e.g., "create", "read", "update", "delete")
50    pub action: AuditAction,
51    /// Outcome of the action (success, failure, denied)
52    pub outcome: String,
53    /// Structured metadata for the audit entry
54    pub metadata: HashMap<String, Value>,
55    /// Associated session identifier
56    pub session_id: Option<SessionId>,
57}
58
59/// Configuration for logging system
60#[derive(Debug, Clone)]
61pub struct LoggingConfig {
62    /// Maximum number of log entries to retain in memory
63    pub max_log_entries: u32,
64    /// Maximum number of audit entries to retain in memory
65    pub max_audit_entries: u32,
66    /// Log level filter (debug, info, warn, error)
67    pub log_level: LogLevel,
68    /// Whether audit logging is enabled
69    pub audit_enabled: bool,
70}
71
72impl Default for LoggingConfig {
73    fn default() -> Self {
74        Self {
75            max_log_entries: 1024,
76            max_audit_entries: 512,
77            log_level: LogLevel::Info,
78            audit_enabled: true,
79        }
80    }
81}
82
83/// Statistics for the logging system
84#[derive(Debug, Clone, Default)]
85pub struct LoggingStats {
86    /// Total number of log entries written
87    pub total_logs: u64,
88    /// Total number of audit entries written
89    pub total_audit_logs: u64,
90    /// Number of error level logs
91    pub error_logs: u64,
92    /// Number of warning level logs
93    pub warn_logs: u64,
94    /// Number of info level logs
95    pub info_logs: u64,
96    /// Number of debug level logs
97    pub debug_logs: u64,
98}
99
100/// Production logging handler for production use
101///
102/// This handler provides system logging by delegating to external logging services.
103/// It is stateless and does not maintain in-memory buffers.
104///
105/// **Note**: Complex log aggregation and multi-component coordination has been
106/// moved to `LoggingCoordinator` in aura-protocol (Layer 4). This handler provides
107/// only stateless logging operations. For coordination capabilities, wrap this handler
108/// with `aura_protocol::handlers::LoggingCoordinator`.
109#[derive(Debug, Clone)]
110pub struct LoggingSystemHandler {
111    /// Configuration for logging operations
112    config: LoggingConfig,
113}
114
115impl LoggingSystemHandler {
116    /// Create a new logging system handler
117    pub fn new(config: LoggingConfig) -> Self {
118        Self { config }
119    }
120
121    /// Create with default configuration
122    pub fn with_defaults() -> Self {
123        Self::new(LoggingConfig::default())
124    }
125
126    /// Create a new logging system handler
127    pub fn new_real(config: LoggingConfig) -> Self {
128        Self::new(config)
129    }
130
131    /// Apply log level filtering and emit to tracing
132    fn apply_level(level: LogLevel, component: &ComponentId, message: &str) {
133        match level {
134            LogLevel::Error => error!("{}: {}", component, message),
135            LogLevel::Warn => warn!("{}: {}", component, message),
136            LogLevel::Info => info!("{}: {}", component, message),
137            LogLevel::Debug => debug!("{}: {}", component, message),
138        }
139    }
140
141    /// Push log entry (stateless - delegates to external logging service)
142    async fn push_log(&self, entry: LogEntry) {
143        tracing::debug!(
144            level = %entry.level,
145            component = %entry.component,
146            message = entry.message,
147            "Log entry sent via logging handler"
148        );
149        let _ = entry;
150    }
151
152    /// Push audit entry (stateless - delegates to external audit service)
153    async fn push_audit(&self, entry: AuditEntry) {
154        tracing::info!(
155            event_type = entry.event_type,
156            actor = ?entry.actor,
157            resource = entry.resource,
158            action = %entry.action,
159            outcome = entry.outcome,
160            "Audit entry sent via logging handler"
161        );
162        let _ = entry;
163    }
164
165    /// Log a structured message
166    pub async fn log_structured(
167        &self,
168        level: LogLevel,
169        component: ComponentId,
170        message: &str,
171        metadata: HashMap<String, Value>,
172        session_id: Option<SessionId>,
173        device_id: Option<DeviceId>,
174        trace_id: Option<Uuid>,
175    ) -> Result<(), SystemError> {
176        let entry = LogEntry {
177            level,
178            message: message.to_string(),
179            component: component.clone(),
180            session_id,
181            device_id,
182            metadata,
183            trace_id,
184        };
185
186        Self::apply_level(level, &component, message);
187        self.push_log(entry).await;
188        Ok(())
189    }
190
191    /// Log an audit event
192    pub async fn audit_log(
193        &self,
194        event_type: &str,
195        actor: Option<DeviceId>,
196        resource: &str,
197        action: AuditAction,
198        outcome: &str,
199        metadata: HashMap<String, Value>,
200        session_id: Option<SessionId>,
201    ) -> Result<(), SystemError> {
202        if !self.config.audit_enabled {
203            return Ok(());
204        }
205
206        let entry = AuditEntry {
207            event_type: event_type.to_string(),
208            actor,
209            resource: resource.to_string(),
210            action,
211            outcome: outcome.to_string(),
212            metadata,
213            session_id,
214        };
215
216        self.push_audit(entry).await;
217        Ok(())
218    }
219
220    /// Get recent logs (stateless - delegates to external service)
221    pub async fn get_recent_logs(&self, count: usize) -> Vec<LogEntry> {
222        let _ = count;
223        Vec::new()
224    }
225
226    /// Get recent audit logs (stateless - delegates to external service)
227    pub async fn get_recent_audit_logs(&self, count: usize) -> Vec<AuditEntry> {
228        let _ = count;
229        Vec::new()
230    }
231
232    /// Get logging statistics (stateless - delegates to external service)
233    pub async fn get_statistics(&self) -> LoggingStats {
234        LoggingStats::default()
235    }
236}
237
238impl Default for LoggingSystemHandler {
239    fn default() -> Self {
240        Self::new(LoggingConfig::default())
241    }
242}
243
244#[async_trait]
245impl SystemEffects for LoggingSystemHandler {
246    async fn log(&self, level: &str, component: &str, message: &str) -> Result<(), SystemError> {
247        let parsed_level = LogLevel::try_from(level).unwrap_or(LogLevel::Info);
248        let component_id = ComponentId::from(component);
249        self.log_structured(
250            parsed_level,
251            component_id,
252            message,
253            HashMap::new(),
254            None,
255            None,
256            None,
257        )
258        .await
259    }
260
261    async fn log_with_context(
262        &self,
263        level: &str,
264        component: &str,
265        message: &str,
266        context: HashMap<String, String>,
267    ) -> Result<(), SystemError> {
268        let metadata: HashMap<String, Value> = context
269            .into_iter()
270            .map(|(k, v)| (k, Value::String(v)))
271            .collect();
272        let parsed_level = LogLevel::try_from(level).unwrap_or(LogLevel::Info);
273        let component_id = ComponentId::from(component);
274        self.log_structured(
275            parsed_level,
276            component_id,
277            message,
278            metadata,
279            None,
280            None,
281            None,
282        )
283        .await
284    }
285
286    async fn get_system_info(&self) -> Result<HashMap<String, String>, SystemError> {
287        let mut info = HashMap::new();
288        info.insert("component".to_string(), "logging".to_string());
289        info.insert("log_level".to_string(), self.config.log_level.to_string());
290        info.insert(
291            "audit_enabled".to_string(),
292            self.config.audit_enabled.to_string(),
293        );
294        info.insert("status".to_string(), "operational".to_string());
295        Ok(info)
296    }
297
298    async fn set_config(&self, key: &str, value: &str) -> Result<(), SystemError> {
299        match key {
300            "log_level" => {
301                // Validate the value but don't store it (stateless handler)
302                LogLevel::try_from(value).map_err(|_| SystemError::InvalidConfiguration {
303                    key: key.to_string(),
304                    value: value.to_string(),
305                })?;
306                Ok(())
307            }
308            "audit_enabled" => {
309                // Validate the value but don't store it (stateless handler)
310                value
311                    .parse::<bool>()
312                    .map_err(|_| SystemError::InvalidConfiguration {
313                        key: key.to_string(),
314                        value: value.to_string(),
315                    })?;
316                Ok(())
317            }
318            _ => Err(SystemError::InvalidConfiguration {
319                key: key.to_string(),
320                value: value.to_string(),
321            }),
322        }
323    }
324
325    async fn get_config(&self, key: &str) -> Result<String, SystemError> {
326        match key {
327            "log_level" => Ok(self.config.log_level.to_string()),
328            "audit_enabled" => Ok(self.config.audit_enabled.to_string()),
329            "max_log_entries" => Ok(self.config.max_log_entries.to_string()),
330            "max_audit_entries" => Ok(self.config.max_audit_entries.to_string()),
331            _ => Err(SystemError::InvalidConfiguration {
332                key: key.to_string(),
333                value: "unknown".to_string(),
334            }),
335        }
336    }
337
338    async fn health_check(&self) -> Result<bool, SystemError> {
339        Ok(true)
340    }
341
342    async fn get_metrics(&self) -> Result<HashMap<String, f64>, SystemError> {
343        let mut metrics = HashMap::new();
344
345        metrics.insert("logs_total".to_string(), 0.0);
346        metrics.insert("audit_logs_total".to_string(), 0.0);
347        metrics.insert("logs_error".to_string(), 0.0);
348        metrics.insert("logs_warn".to_string(), 0.0);
349        metrics.insert("logs_info".to_string(), 0.0);
350        metrics.insert("logs_debug".to_string(), 0.0);
351        metrics.insert("recent_logs".to_string(), 0.0);
352        metrics.insert("recent_audit_logs".to_string(), 0.0);
353        metrics.insert(
354            "max_log_entries_configured".to_string(),
355            self.config.max_log_entries as f64,
356        );
357        metrics.insert(
358            "max_audit_entries_configured".to_string(),
359            self.config.max_audit_entries as f64,
360        );
361        Ok(metrics)
362    }
363
364    async fn restart_component(&self, _component: &str) -> Result<(), SystemError> {
365        warn!("Restart not implemented for logging system handler");
366        Ok(())
367    }
368
369    async fn shutdown(&self) -> Result<(), SystemError> {
370        Ok(())
371    }
372}
373
374#[cfg(test)]
375#[allow(clippy::expect_used)] // Test code: expect() is acceptable for test assertions
376mod tests {
377    use super::*;
378    use serde_json::json;
379
380    #[tokio::test]
381    async fn test_logging_handler_creation() {
382        let handler = LoggingSystemHandler::new(LoggingConfig::default());
383        // LoggingSystemHandler should be created successfully
384        assert_eq!(handler.config.log_level, LogLevel::Info);
385        assert!(handler.config.audit_enabled);
386    }
387
388    #[tokio::test]
389    async fn test_basic_logging() {
390        let handler = LoggingSystemHandler::new(LoggingConfig::default());
391
392        handler
393            .log("info", "test", "hello world")
394            .await
395            .expect("log ok");
396        handler
397            .log_with_context(
398                "warn",
399                "test",
400                "with context",
401                HashMap::from([("key".into(), "value".into())]),
402            )
403            .await
404            .expect("log ok");
405
406        // Test system effects
407        let info = handler.get_system_info().await.unwrap();
408        assert_eq!(info.get("component"), Some(&"logging".to_string()));
409    }
410
411    #[tokio::test]
412    async fn test_audit_logging() {
413        let handler = LoggingSystemHandler::new(LoggingConfig::default());
414
415        handler
416            .audit_log(
417                "authentication",
418                Some(DeviceId::new_from_entropy([3u8; 32])),
419                "resource",
420                AuditAction::Custom("action".to_string()),
421                "success",
422                HashMap::from([("extra".into(), json!("1"))]),
423                None,
424            )
425            .await
426            .expect("audit ok");
427
428        // Test config operations
429        let config_value = handler.get_config("log_level").await.unwrap();
430        assert_eq!(config_value, "info");
431    }
432}