systemprompt_logging/models/
log_entry.rs1use 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 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}