Skip to main content

systemprompt_logging/models/
log_row.rs

1use 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}