Skip to main content

systemprompt_logging/models/
log_row.rs

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