systemprompt_logging/models/
log_row.rs1use chrono::{DateTime, Utc};
2use sqlx::FromRow;
3use systemprompt_identifiers::LogId;
4
5use super::{LogEntry, LogLevel, LoggingError};
6
7type Result<T> = std::result::Result<T, LoggingError>;
8
9#[derive(Debug, FromRow)]
10pub struct LogRow {
11 pub id: LogId,
12 pub timestamp: DateTime<Utc>,
13 pub level: String,
14 pub module: String,
15 pub message: String,
16 pub metadata: Option<String>,
17 pub user_id: Option<String>,
18 pub session_id: Option<String>,
19 pub task_id: Option<String>,
20 pub trace_id: Option<String>,
21 pub context_id: Option<String>,
22 pub client_id: Option<String>,
23}
24
25impl LogRow {
26 pub fn from_json_row(row: &systemprompt_database::JsonRow) -> Result<Self> {
27 let missing = |col: &str| LoggingError::MissingColumn {
28 column: col.to_string(),
29 };
30
31 let id = row
32 .get("id")
33 .and_then(|v| v.as_str())
34 .map(LogId::new)
35 .ok_or_else(|| missing("id"))?;
36
37 let timestamp = row
38 .get("timestamp")
39 .and_then(systemprompt_database::parse_database_datetime)
40 .ok_or_else(|| missing("timestamp"))?;
41
42 let level = row
43 .get("level")
44 .and_then(|v| v.as_str())
45 .ok_or_else(|| missing("level"))?
46 .to_string();
47
48 let module = row
49 .get("module")
50 .and_then(|v| v.as_str())
51 .ok_or_else(|| missing("module"))?
52 .to_string();
53
54 let message = row
55 .get("message")
56 .and_then(|v| v.as_str())
57 .ok_or_else(|| missing("message"))?
58 .to_string();
59
60 let metadata = row
61 .get("metadata")
62 .and_then(|v| v.as_str())
63 .map(String::from);
64
65 let user_id = row
66 .get("user_id")
67 .and_then(|v| v.as_str())
68 .map(String::from);
69
70 let session_id = row
71 .get("session_id")
72 .and_then(|v| v.as_str())
73 .map(String::from);
74
75 let task_id = row
76 .get("task_id")
77 .and_then(|v| v.as_str())
78 .map(String::from);
79
80 let trace_id = row
81 .get("trace_id")
82 .and_then(|v| v.as_str())
83 .map(String::from);
84
85 let context_id = row
86 .get("context_id")
87 .and_then(|v| v.as_str())
88 .map(String::from);
89
90 let client_id = row
91 .get("client_id")
92 .and_then(|v| v.as_str())
93 .map(String::from);
94
95 Ok(Self {
96 id,
97 timestamp,
98 level,
99 module,
100 message,
101 metadata,
102 user_id,
103 session_id,
104 task_id,
105 trace_id,
106 context_id,
107 client_id,
108 })
109 }
110}
111
112impl From<LogRow> for LogEntry {
113 fn from(row: LogRow) -> Self {
114 let level = row.level.parse().unwrap_or(LogLevel::Info);
115
116 Self {
117 id: row.id,
118 timestamp: row.timestamp,
119 level,
120 module: row.module,
121 message: row.message,
122 metadata: row.metadata.and_then(|s| {
123 serde_json::from_str(&s)
124 .map_err(|e| {
125 tracing::warn!(error = %e, "Malformed log metadata JSON");
126 e
127 })
128 .ok()
129 }),
130 user_id: row.user_id.map_or_else(
131 systemprompt_identifiers::UserId::system,
132 systemprompt_identifiers::UserId::new,
133 ),
134 session_id: row.session_id.map_or_else(
135 systemprompt_identifiers::SessionId::system,
136 systemprompt_identifiers::SessionId::new,
137 ),
138 task_id: row.task_id.map(systemprompt_identifiers::TaskId::new),
139 trace_id: row.trace_id.map_or_else(
140 systemprompt_identifiers::TraceId::system,
141 systemprompt_identifiers::TraceId::new,
142 ),
143 context_id: row.context_id.map(systemprompt_identifiers::ContextId::new),
144 client_id: row.client_id.map(systemprompt_identifiers::ClientId::new),
145 }
146 }
147}