rust_secure_logger/
entry.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
9pub enum SecurityLevel {
10 Info,
12 Warning,
14 SecurityEvent,
16 Critical,
18 Audit,
20}
21
22impl SecurityLevel {
23 pub fn as_str(&self) -> &'static str {
25 match self {
26 SecurityLevel::Info => "INFO",
27 SecurityLevel::Warning => "WARNING",
28 SecurityLevel::SecurityEvent => "SECURITY_EVENT",
29 SecurityLevel::Critical => "CRITICAL",
30 SecurityLevel::Audit => "AUDIT",
31 }
32 }
33
34 pub fn severity(&self) -> u8 {
36 match self {
37 SecurityLevel::Info => 1,
38 SecurityLevel::Warning => 3,
39 SecurityLevel::SecurityEvent => 5,
40 SecurityLevel::Critical => 8,
41 SecurityLevel::Audit => 6,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct LogEntry {
49 pub timestamp: DateTime<Utc>,
51 pub level: SecurityLevel,
53 pub message: String,
55 pub metadata: Option<serde_json::Value>,
57 pub integrity_hash: String,
59 pub source: Option<String>,
61 pub category: Option<String>,
63}
64
65impl LogEntry {
66 pub fn new(level: SecurityLevel, message: String, metadata: Option<serde_json::Value>) -> Self {
68 let timestamp = Utc::now();
69 let mut entry = Self {
70 timestamp,
71 level,
72 message,
73 metadata,
74 integrity_hash: String::new(),
75 source: None,
76 category: None,
77 };
78 entry.integrity_hash = entry.calculate_hash();
79 entry
80 }
81
82 pub fn new_with_context(
84 level: SecurityLevel,
85 message: String,
86 metadata: Option<serde_json::Value>,
87 source: Option<String>,
88 category: Option<String>,
89 ) -> Self {
90 let timestamp = Utc::now();
91 let mut entry = Self {
92 timestamp,
93 level,
94 message,
95 metadata,
96 integrity_hash: String::new(),
97 source,
98 category,
99 };
100 entry.integrity_hash = entry.calculate_hash();
101 entry
102 }
103
104 fn calculate_hash(&self) -> String {
106 let mut hasher = Sha256::new();
107 hasher.update(self.timestamp.to_rfc3339().as_bytes());
108 hasher.update(format!("{:?}", self.level).as_bytes());
109 hasher.update(self.message.as_bytes());
110 if let Some(ref meta) = self.metadata {
111 hasher.update(meta.to_string().as_bytes());
112 }
113 if let Some(ref source) = self.source {
114 hasher.update(source.as_bytes());
115 }
116 if let Some(ref category) = self.category {
117 hasher.update(category.as_bytes());
118 }
119 format!("{:x}", hasher.finalize())
120 }
121
122 pub fn verify_integrity(&self) -> bool {
124 let calculated = self.calculate_hash();
125 calculated == self.integrity_hash
126 }
127
128 pub fn to_json(&self) -> Result<String, serde_json::Error> {
130 serde_json::to_string(self)
131 }
132
133 pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
135 serde_json::to_string_pretty(self)
136 }
137
138 pub fn to_log_line(&self) -> String {
140 format!(
141 "[{}] [{}] {} {}",
142 self.timestamp.to_rfc3339(),
143 self.level.as_str(),
144 self.message,
145 if let Some(ref meta) = self.metadata {
146 format!("| metadata: {}", meta)
147 } else {
148 String::new()
149 }
150 )
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_security_level_string() {
160 assert_eq!(SecurityLevel::Info.as_str(), "INFO");
161 assert_eq!(SecurityLevel::Critical.as_str(), "CRITICAL");
162 }
163
164 #[test]
165 fn test_security_level_severity() {
166 assert_eq!(SecurityLevel::Info.severity(), 1);
167 assert_eq!(SecurityLevel::Critical.severity(), 8);
168 }
169
170 #[test]
171 fn test_log_entry_creation() {
172 let entry = LogEntry::new(SecurityLevel::Info, "Test message".to_string(), None);
173 assert_eq!(entry.level, SecurityLevel::Info);
174 assert_eq!(entry.message, "Test message");
175 assert!(!entry.integrity_hash.is_empty());
176 }
177
178 #[test]
179 fn test_integrity_verification() {
180 let entry = LogEntry::new(SecurityLevel::Audit, "Transaction: $1000".to_string(), None);
181 assert!(entry.verify_integrity());
182 }
183
184 #[test]
185 fn test_tampering_detection() {
186 let mut entry = LogEntry::new(SecurityLevel::Audit, "Original message".to_string(), None);
187 entry.message = "Tampered message".to_string();
188 assert!(!entry.verify_integrity());
189 }
190
191 #[test]
192 fn test_entry_with_context() {
193 let entry = LogEntry::new_with_context(
194 SecurityLevel::SecurityEvent,
195 "Login failed".to_string(),
196 None,
197 Some("web-server-01".to_string()),
198 Some("authentication".to_string()),
199 );
200 assert_eq!(entry.source, Some("web-server-01".to_string()));
201 assert_eq!(entry.category, Some("authentication".to_string()));
202 assert!(entry.verify_integrity());
203 }
204
205 #[test]
206 fn test_to_log_line() {
207 let entry = LogEntry::new(SecurityLevel::Warning, "High CPU usage".to_string(), None);
208 let log_line = entry.to_log_line();
209 assert!(log_line.contains("WARNING"));
210 assert!(log_line.contains("High CPU usage"));
211 }
212}