Skip to main content

systemprompt_logging/models/
log_row.rs

1use chrono::{DateTime, Utc};
2use sqlx::FromRow;
3use systemprompt_identifiers::{ClientId, ContextId, LogId, SessionId, TaskId, TraceId, UserId};
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<UserId>,
18    pub session_id: Option<SessionId>,
19    pub task_id: Option<TaskId>,
20    pub trace_id: Option<TraceId>,
21    pub context_id: Option<ContextId>,
22    pub client_id: Option<ClientId>,
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.get("user_id").and_then(|v| v.as_str()).map(UserId::new);
66
67        let session_id = row
68            .get("session_id")
69            .and_then(|v| v.as_str())
70            .map(SessionId::new);
71
72        let task_id = row.get("task_id").and_then(|v| v.as_str()).map(TaskId::new);
73
74        let trace_id = row
75            .get("trace_id")
76            .and_then(|v| v.as_str())
77            .map(TraceId::new);
78
79        let context_id = row
80            .get("context_id")
81            .and_then(|v| v.as_str())
82            .map(ContextId::new);
83
84        let client_id = row
85            .get("client_id")
86            .and_then(|v| v.as_str())
87            .map(ClientId::new);
88
89        Ok(Self {
90            id,
91            timestamp,
92            level,
93            module,
94            message,
95            metadata,
96            user_id,
97            session_id,
98            task_id,
99            trace_id,
100            context_id,
101            client_id,
102        })
103    }
104}
105
106impl From<LogRow> for LogEntry {
107    fn from(row: LogRow) -> Self {
108        let level = row.level.parse().unwrap_or(LogLevel::Info);
109
110        Self {
111            id: row.id,
112            timestamp: row.timestamp,
113            level,
114            module: row.module,
115            message: row.message,
116            metadata: row.metadata.and_then(|s| {
117                serde_json::from_str(&s)
118                    .map_err(|e| {
119                        tracing::warn!(error = %e, "Malformed log metadata JSON");
120                        e
121                    })
122                    .ok()
123            }),
124            user_id: row.user_id.unwrap_or_else(UserId::system),
125            session_id: row.session_id.unwrap_or_else(SessionId::system),
126            task_id: row.task_id,
127            trace_id: row.trace_id.unwrap_or_else(TraceId::system),
128            context_id: row.context_id,
129            client_id: row.client_id,
130        }
131    }
132}