1use chrono::Utc;
2use serde::Serialize;
3use std::collections::HashMap;
4use tracing::{error, info, warn};
5use uuid::Uuid;
6
7use crate::crypto::EncryptionAlgorithm;
8use crate::manifest::Manifest;
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(rename_all = "snake_case")]
12pub enum LogLevel {
13 Info,
14 Warn,
15 Error,
16 Debug,
17}
18
19#[derive(Debug, Clone, Serialize)]
20pub struct AuditEvent {
21 pub timestamp: String,
22 pub event_type: String,
23 pub package_id: Option<Uuid>,
24 pub details: HashMap<String, serde_json::Value>,
25}
26
27impl AuditEvent {
28 pub fn new(event_type: impl Into<String>) -> Self {
29 Self {
30 timestamp: Utc::now().to_rfc3339(),
31 event_type: event_type.into(),
32 package_id: None,
33 details: HashMap::new(),
34 }
35 }
36
37 pub fn with_package_id(mut self, id: Uuid) -> Self {
38 self.package_id = Some(id);
39 self
40 }
41
42 pub fn with_detail(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
43 let key = key.into();
44 let value = serde_json::to_value(value).unwrap_or_default();
45 self.details.insert(key, value);
46 self
47 }
48
49 pub fn log(&self) {
50 let json = serde_json::to_string(self).unwrap_or_default();
53 match self.event_type.as_str() {
54 "error" | "decryption_failed" | "verification_failed" | "execution_failed" => {
55 error!(target: "crablock_audit", "{}", json);
56 }
57 "warn" | "cleanup_failed" => {
58 warn!(target: "crablock_audit", "{}", json);
59 }
60 _ => {
61 info!(target: "crablock_audit", "{}", json);
62 }
63 }
64 }
65}
66
67pub fn log_package_load(manifest: &Manifest, key_source: &str, decrypt_mode: &str) {
68 AuditEvent::new("package_loaded")
69 .with_package_id(manifest.package_id)
70 .with_detail("artifact_name", &manifest.artifact_name)
71 .with_detail(
72 "encryption_algorithm",
73 manifest.encryption_algorithm.as_str(),
74 )
75 .with_detail("key_source_type", key_source)
76 .with_detail("decrypt_mode", decrypt_mode)
77 .with_detail("version", &manifest.version)
78 .log();
79}
80
81pub fn log_verification_result(package_id: Uuid, success: bool, details: &str) {
82 let event_type = if success {
83 "verification_success"
84 } else {
85 "verification_failed"
86 };
87 AuditEvent::new(event_type)
88 .with_package_id(package_id)
89 .with_detail("success", success)
90 .with_detail("details", details)
91 .log();
92}
93
94pub fn log_execution_start(package_id: Uuid, entrypoint: &str, pid: u32) {
95 AuditEvent::new("execution_start")
96 .with_package_id(package_id)
97 .with_detail("entrypoint", entrypoint)
98 .with_detail("pid", pid)
99 .log();
100}
101
102pub fn log_execution_stop(package_id: Uuid, exit_code: i32) {
103 AuditEvent::new("execution_stop")
104 .with_package_id(package_id)
105 .with_detail("exit_code", exit_code)
106 .log();
107}
108
109pub fn log_decryption(package_id: Uuid, algorithm: EncryptionAlgorithm, mode: &str) {
110 AuditEvent::new("decryption")
111 .with_package_id(package_id)
112 .with_detail("algorithm", algorithm.as_str())
113 .with_detail("mode", mode)
114 .log();
115}
116
117pub fn log_cleanup(package_id: Uuid, path: &str, success: bool) {
118 let event = if success {
119 "cleanup_success"
120 } else {
121 "cleanup_failed"
122 };
123 AuditEvent::new(event)
124 .with_package_id(package_id)
125 .with_detail("path", path)
126 .with_detail("success", success)
127 .log();
128}
129
130pub fn init_logging(json_format: bool) {
131 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
132
133 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
135
136 if json_format {
137 tracing_subscriber::registry()
138 .with(env_filter)
139 .with(fmt::layer().json())
140 .init();
141 } else {
142 tracing_subscriber::registry()
143 .with(env_filter)
144 .with(fmt::layer())
145 .init();
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_audit_event_creation() {
155 let event = AuditEvent::new("test_event")
156 .with_package_id(Uuid::new_v4())
157 .with_detail("key", "value");
158
159 assert_eq!(event.event_type, "test_event");
160 assert!(event.package_id.is_some());
161 assert!(event.details.contains_key("key"));
162 }
163}