Skip to main content

crablock_core/
logging.rs

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        // We always serialize the same event shape.
51        // Only the log level changes based on the event type.
52        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    // Default to `info` so the CLI is useful even when RUST_LOG is not set.
134    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}