1use 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23pub struct LogEntry {
24 pub level: LogLevel,
26 pub message: String,
28 pub component: ComponentId,
30 pub session_id: Option<SessionId>,
32 pub device_id: Option<DeviceId>,
34 pub metadata: HashMap<String, Value>,
36 pub trace_id: Option<Uuid>,
38}
39
40#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
42pub struct AuditEntry {
43 pub event_type: String,
45 pub actor: Option<DeviceId>,
47 pub resource: String,
49 pub action: AuditAction,
51 pub outcome: String,
53 pub metadata: HashMap<String, Value>,
55 pub session_id: Option<SessionId>,
57}
58
59#[derive(Debug, Clone)]
61pub struct LoggingConfig {
62 pub max_log_entries: u32,
64 pub max_audit_entries: u32,
66 pub log_level: LogLevel,
68 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#[derive(Debug, Clone, Default)]
85pub struct LoggingStats {
86 pub total_logs: u64,
88 pub total_audit_logs: u64,
90 pub error_logs: u64,
92 pub warn_logs: u64,
94 pub info_logs: u64,
96 pub debug_logs: u64,
98}
99
100#[derive(Debug, Clone)]
110pub struct LoggingSystemHandler {
111 config: LoggingConfig,
113}
114
115impl LoggingSystemHandler {
116 pub fn new(config: LoggingConfig) -> Self {
118 Self { config }
119 }
120
121 pub fn with_defaults() -> Self {
123 Self::new(LoggingConfig::default())
124 }
125
126 pub fn new_real(config: LoggingConfig) -> Self {
128 Self::new(config)
129 }
130
131 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 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 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 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 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 pub async fn get_recent_logs(&self, count: usize) -> Vec<LogEntry> {
222 let _ = count;
223 Vec::new()
224 }
225
226 pub async fn get_recent_audit_logs(&self, count: usize) -> Vec<AuditEntry> {
228 let _ = count;
229 Vec::new()
230 }
231
232 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 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 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)] mod 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 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 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 let config_value = handler.get_config("log_level").await.unwrap();
430 assert_eq!(config_value, "info");
431 }
432}