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