systemprompt_logging/models/
log_row.rs1use 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}