1use serde::Serialize;
12use std::fmt;
13use tracing::{error, info, warn, Level};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LogLevel {
18 Trace,
19 Debug,
20 Info,
21 Warn,
22 Error,
23}
24
25impl LogLevel {
26 pub fn from_str(s: &str) -> Self {
27 match s.to_lowercase().as_str() {
28 "trace" => LogLevel::Trace,
29 "debug" => LogLevel::Debug,
30 "info" => LogLevel::Info,
31 "warn" | "warning" => LogLevel::Warn,
32 "error" => LogLevel::Error,
33 _ => LogLevel::Info,
34 }
35 }
36
37 pub fn to_tracing_level(&self) -> Level {
38 match self {
39 LogLevel::Trace => Level::TRACE,
40 LogLevel::Debug => Level::DEBUG,
41 LogLevel::Info => Level::INFO,
42 LogLevel::Warn => Level::WARN,
43 LogLevel::Error => Level::ERROR,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct LoggingConfig {
51 pub level: LogLevel,
53
54 pub json_format: bool,
56
57 pub file_output: bool,
59
60 pub file_path: Option<String>,
62
63 pub include_timestamps: bool,
65
66 pub include_thread_ids: bool,
68
69 pub include_source: bool,
71}
72
73impl Default for LoggingConfig {
74 fn default() -> Self {
75 Self {
76 level: LogLevel::Info,
77 json_format: std::env::var("LOG_JSON").unwrap_or_else(|_| "false".to_string()) == "true",
78 file_output: false,
79 file_path: None,
80 include_timestamps: true,
81 include_thread_ids: true,
82 include_source: true,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize)]
89pub struct LogEntry {
90 pub timestamp: String,
91 pub level: String,
92 pub message: String,
93 pub target: String,
94 pub thread_id: Option<String>,
95 pub file: Option<String>,
96 pub line: Option<u32>,
97 pub fields: serde_json::Value,
98}
99
100#[derive(Debug, Clone, Serialize)]
102pub struct ErrorContext {
103 pub error_type: String,
104 pub error_message: String,
105 pub component: String,
106 pub severity: String,
107 pub trace_id: Option<String>,
108 pub additional_context: serde_json::Value,
109}
110
111impl ErrorContext {
112 pub fn new(
113 error_type: impl ToString,
114 error_message: impl ToString,
115 component: impl ToString,
116 ) -> Self {
117 Self {
118 error_type: error_type.to_string(),
119 error_message: error_message.to_string(),
120 component: component.to_string(),
121 severity: "error".to_string(),
122 trace_id: None,
123 additional_context: serde_json::json!({}),
124 }
125 }
126
127 pub fn with_severity(mut self, severity: impl ToString) -> Self {
128 self.severity = severity.to_string();
129 self
130 }
131
132 pub fn with_trace_id(mut self, trace_id: impl ToString) -> Self {
133 self.trace_id = Some(trace_id.to_string());
134 self
135 }
136
137 pub fn with_context(mut self, key: &str, value: serde_json::Value) -> Self {
138 if let Some(obj) = self.additional_context.as_object_mut() {
139 obj.insert(key.to_string(), value);
140 }
141 self
142 }
143
144 pub fn log(&self) {
145 error!(
146 error_type = %self.error_type,
147 error_message = %self.error_message,
148 component = %self.component,
149 severity = %self.severity,
150 trace_id = ?self.trace_id,
151 context = ?self.additional_context,
152 "Error occurred"
153 );
154 }
155}
156
157pub struct AgentLogger {
159 agent_name: String,
160}
161
162impl AgentLogger {
163 pub fn new(agent_name: impl ToString) -> Self {
164 Self {
165 agent_name: agent_name.to_string(),
166 }
167 }
168
169 pub fn info(&self, message: &str) {
170 info!(agent = %self.agent_name, "{}", message);
171 }
172
173 pub fn warn(&self, message: &str) {
174 warn!(agent = %self.agent_name, "{}", message);
175 }
176
177 pub fn error(&self, message: &str, error_context: Option<&ErrorContext>) {
178 if let Some(ctx) = error_context {
179 error!(
180 agent = %self.agent_name,
181 error_type = %ctx.error_type,
182 error_message = %ctx.error_message,
183 severity = %ctx.severity,
184 "{}",
185 message
186 );
187 } else {
188 error!(agent = %self.agent_name, "{}", message);
189 }
190 }
191
192 pub fn debug(&self, message: &str) {
193 tracing::debug!(agent = %self.agent_name, "{}", message);
194 }
195
196 pub fn trace(&self, message: &str) {
197 tracing::trace!(agent = %self.agent_name, "{}", message);
198 }
199}
200
201pub struct PipelineLogger {
203 pipeline_id: String,
204}
205
206impl PipelineLogger {
207 pub fn new(pipeline_id: impl ToString) -> Self {
208 Self {
209 pipeline_id: pipeline_id.to_string(),
210 }
211 }
212
213 pub fn phase_start(&self, phase_name: &str) {
214 info!(
215 pipeline_id = %self.pipeline_id,
216 phase = %phase_name,
217 "Starting pipeline phase"
218 );
219 }
220
221 pub fn phase_complete(&self, phase_name: &str, duration_ms: f64) {
222 info!(
223 pipeline_id = %self.pipeline_id,
224 phase = %phase_name,
225 duration_ms = duration_ms,
226 "Pipeline phase completed"
227 );
228 }
229
230 pub fn phase_error(&self, phase_name: &str, error: &str) {
231 error!(
232 pipeline_id = %self.pipeline_id,
233 phase = %phase_name,
234 error = %error,
235 "Pipeline phase failed"
236 );
237 }
238}
239
240pub struct TranslationLogger {
242 translation_id: String,
243}
244
245impl TranslationLogger {
246 pub fn new(translation_id: impl ToString) -> Self {
247 Self {
248 translation_id: translation_id.to_string(),
249 }
250 }
251
252 pub fn start(&self, source_lang: &str, target_format: &str, lines: usize) {
253 info!(
254 translation_id = %self.translation_id,
255 source_language = %source_lang,
256 target_format = %target_format,
257 lines_of_code = lines,
258 "Starting translation"
259 );
260 }
261
262 pub fn progress(&self, step: &str, progress_percent: f64) {
263 info!(
264 translation_id = %self.translation_id,
265 step = %step,
266 progress_percent = progress_percent,
267 "Translation progress"
268 );
269 }
270
271 pub fn complete(&self, duration_ms: f64, output_size_bytes: usize) {
272 info!(
273 translation_id = %self.translation_id,
274 duration_ms = duration_ms,
275 output_size_bytes = output_size_bytes,
276 "Translation completed successfully"
277 );
278 }
279
280 pub fn failed(&self, error_category: &str, error_message: &str) {
281 error!(
282 translation_id = %self.translation_id,
283 error_category = %error_category,
284 error_message = %error_message,
285 "Translation failed"
286 );
287 }
288}
289
290pub struct PerformanceLogger;
292
293impl PerformanceLogger {
294 pub fn log_latency(operation: &str, duration_ms: f64) {
295 info!(
296 operation = %operation,
297 duration_ms = duration_ms,
298 metric_type = "latency",
299 "Performance metric"
300 );
301 }
302
303 pub fn log_throughput(operation: &str, items_per_second: f64) {
304 info!(
305 operation = %operation,
306 items_per_second = items_per_second,
307 metric_type = "throughput",
308 "Performance metric"
309 );
310 }
311
312 pub fn log_resource_usage(component: &str, cpu_percent: f64, memory_mb: f64) {
313 info!(
314 component = %component,
315 cpu_percent = cpu_percent,
316 memory_mb = memory_mb,
317 metric_type = "resource_usage",
318 "Resource usage"
319 );
320 }
321}
322
323pub struct AuditLogger;
325
326impl AuditLogger {
327 pub fn log_translation_request(user_id: &str, source_file: &str, ip_address: &str) {
328 info!(
329 user_id = %user_id,
330 source_file = %source_file,
331 ip_address = %ip_address,
332 event_type = "translation_request",
333 "Audit log"
334 );
335 }
336
337 pub fn log_translation_complete(user_id: &str, translation_id: &str, success: bool) {
338 info!(
339 user_id = %user_id,
340 translation_id = %translation_id,
341 success = success,
342 event_type = "translation_complete",
343 "Audit log"
344 );
345 }
346
347 pub fn log_error(user_id: &str, error_type: &str, details: &str) {
348 error!(
349 user_id = %user_id,
350 error_type = %error_type,
351 details = %details,
352 event_type = "error",
353 "Audit log"
354 );
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_log_level_from_str() {
364 assert_eq!(LogLevel::from_str("info"), LogLevel::Info);
365 assert_eq!(LogLevel::from_str("DEBUG"), LogLevel::Debug);
366 assert_eq!(LogLevel::from_str("error"), LogLevel::Error);
367 assert_eq!(LogLevel::from_str("invalid"), LogLevel::Info);
368 }
369
370 #[test]
371 fn test_logging_config_default() {
372 let config = LoggingConfig::default();
373 assert_eq!(config.level, LogLevel::Info);
374 assert!(config.include_timestamps);
375 }
376
377 #[test]
378 fn test_error_context_builder() {
379 let ctx = ErrorContext::new("ParseError", "Invalid syntax", "ingest-agent")
380 .with_severity("critical")
381 .with_trace_id("trace-123");
382
383 assert_eq!(ctx.error_type, "ParseError");
384 assert_eq!(ctx.severity, "critical");
385 assert_eq!(ctx.trace_id, Some("trace-123".to_string()));
386 }
387
388 #[test]
389 fn test_agent_logger() {
390 let logger = AgentLogger::new("test-agent");
391 assert_eq!(logger.agent_name, "test-agent");
392 }
393
394 #[test]
395 fn test_pipeline_logger() {
396 let logger = PipelineLogger::new("pipeline-123");
397 assert_eq!(logger.pipeline_id, "pipeline-123");
398 }
399
400 #[test]
401 fn test_translation_logger() {
402 let logger = TranslationLogger::new("trans-456");
403 assert_eq!(logger.translation_id, "trans-456");
404 }
405}