Skip to main content

systemprompt_logging/models/
log_entry.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use systemprompt_identifiers::LogId;
4
5use super::{LogLevel, LoggingError};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LogEntry {
9    pub id: LogId,
10    pub timestamp: DateTime<Utc>,
11    pub level: LogLevel,
12    pub module: String,
13    pub message: String,
14    // JSON: structured log metadata — heterogeneous by design
15    pub metadata: Option<serde_json::Value>,
16    pub user_id: systemprompt_identifiers::UserId,
17    pub session_id: systemprompt_identifiers::SessionId,
18    pub task_id: Option<systemprompt_identifiers::TaskId>,
19    pub trace_id: systemprompt_identifiers::TraceId,
20    pub context_id: Option<systemprompt_identifiers::ContextId>,
21    pub client_id: Option<systemprompt_identifiers::ClientId>,
22}
23
24impl LogEntry {
25    pub fn new(level: LogLevel, module: impl Into<String>, message: impl Into<String>) -> Self {
26        Self {
27            id: LogId::generate(),
28            timestamp: Utc::now(),
29            level,
30            module: module.into(),
31            message: message.into(),
32            metadata: None,
33            user_id: systemprompt_identifiers::UserId::system(),
34            session_id: systemprompt_identifiers::SessionId::system(),
35            task_id: None,
36            trace_id: systemprompt_identifiers::TraceId::system(),
37            context_id: None,
38            client_id: None,
39        }
40    }
41
42    #[must_use]
43    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
44        self.metadata = Some(metadata);
45        self
46    }
47
48    #[must_use]
49    pub fn with_user_id(mut self, user_id: systemprompt_identifiers::UserId) -> Self {
50        self.user_id = user_id;
51        self
52    }
53
54    #[must_use]
55    pub fn with_session_id(mut self, session_id: systemprompt_identifiers::SessionId) -> Self {
56        self.session_id = session_id;
57        self
58    }
59
60    #[must_use]
61    pub fn with_task_id(mut self, task_id: systemprompt_identifiers::TaskId) -> Self {
62        self.task_id = Some(task_id);
63        self
64    }
65
66    #[must_use]
67    pub fn with_trace_id(mut self, trace_id: systemprompt_identifiers::TraceId) -> Self {
68        self.trace_id = trace_id;
69        self
70    }
71
72    #[must_use]
73    pub fn with_context_id(mut self, context_id: systemprompt_identifiers::ContextId) -> Self {
74        self.context_id = Some(context_id);
75        self
76    }
77
78    #[must_use]
79    pub fn with_client_id(mut self, client_id: systemprompt_identifiers::ClientId) -> Self {
80        self.client_id = Some(client_id);
81        self
82    }
83
84    pub fn validate(&self) -> Result<(), LoggingError> {
85        if self.module.is_empty() {
86            return Err(LoggingError::EmptyModuleName);
87        }
88        if self.message.is_empty() {
89            return Err(LoggingError::EmptyMessage);
90        }
91        if let Some(metadata) = &self.metadata {
92            if !metadata.is_object()
93                && !metadata.is_array()
94                && !metadata.is_string()
95                && !metadata.is_null()
96            {
97                return Err(LoggingError::InvalidMetadata);
98            }
99        }
100        Ok(())
101    }
102}
103
104impl std::fmt::Display for LogEntry {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        let level_str = match self.level {
107            LogLevel::Error => "ERROR",
108            LogLevel::Warn => "WARN ",
109            LogLevel::Info => "INFO ",
110            LogLevel::Debug => "DEBUG",
111            LogLevel::Trace => "TRACE",
112        };
113
114        let timestamp_str = self.timestamp.format("%H:%M:%S");
115
116        if let Some(metadata) = &self.metadata {
117            write!(
118                f,
119                "{} [{}] {}: {} {}",
120                timestamp_str,
121                level_str,
122                self.module,
123                self.message,
124                serde_json::to_string(metadata).unwrap_or_else(|_| String::new())
125            )
126        } else {
127            write!(
128                f,
129                "{} [{}] {}: {}",
130                timestamp_str, level_str, self.module, self.message
131            )
132        }
133    }
134}