systemprompt_logging/repository/analytics/
mod.rs1use chrono::Utc;
12use serde_json::Value;
13use sqlx::PgPool;
14use std::sync::Arc;
15use systemprompt_database::DbPool;
16use systemprompt_identifiers::{AgentId, ContextId, SessionId, TaskId, UserId};
17
18use crate::models::LoggingError;
19
20mod ingestion;
21
22#[derive(Debug, Clone)]
23pub struct AnalyticsRepository {
24 write_pool: Arc<PgPool>,
25}
26
27impl AnalyticsRepository {
28 pub fn new(db: &DbPool) -> Self {
29 let write_pool = db.write_pool();
30 Self { write_pool }
31 }
32
33 pub async fn log_event(&self, event: &AnalyticsEvent) -> Result<i64, LoggingError> {
34 let result = execute_insert(&self.write_pool, event).await?;
35 Ok(i64::try_from(result).unwrap_or(i64::MAX))
36 }
37}
38
39async fn execute_insert(pool: &PgPool, event: &AnalyticsEvent) -> Result<u64, LoggingError> {
40 let params = EventParams::from(event);
41 run_insert_query(pool, params).await
42}
43
44async fn run_insert_query(pool: &PgPool, p: EventParams<'_>) -> Result<u64, LoggingError> {
45 let result = sqlx::query!(
46 r"
47 INSERT INTO analytics_events
48 (user_id, session_id, context_id, event_type, event_category, severity,
49 endpoint, error_code, response_time_ms, agent_id, task_id, message, metadata, timestamp)
50 VALUES
51 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
52 ",
53 p.user_id.as_str(),
54 p.session_id.as_str(),
55 p.context_id.as_str(),
56 p.event_type,
57 p.event_category,
58 p.severity,
59 p.endpoint,
60 p.error_code,
61 p.response_time_ms,
62 p.agent_id.map(AgentId::as_str),
63 p.task_id.map(TaskId::as_str),
64 p.message,
65 p.metadata,
66 p.timestamp
67 )
68 .execute(pool)
69 .await?;
70 Ok(result.rows_affected())
71}
72
73struct EventParams<'a> {
74 user_id: &'a UserId,
75 session_id: &'a SessionId,
76 context_id: &'a ContextId,
77 event_type: &'a str,
78 event_category: &'a str,
79 severity: &'a str,
80 agent_id: Option<&'a AgentId>,
81 task_id: Option<&'a TaskId>,
82 endpoint: Option<&'a str>,
83 message: Option<&'a str>,
84 error_code: Option<i32>,
85 response_time_ms: Option<i32>,
86 metadata: String,
87 timestamp: chrono::DateTime<Utc>,
88}
89
90impl<'a> From<&'a AnalyticsEvent> for EventParams<'a> {
91 fn from(event: &'a AnalyticsEvent) -> Self {
92 Self {
93 user_id: &event.user_id,
94 session_id: &event.session_id,
95 context_id: &event.context_id,
96 event_type: &event.event_type,
97 event_category: &event.event_category,
98 severity: &event.severity,
99 agent_id: event.agent_id.as_ref(),
100 task_id: event.task_id.as_ref(),
101 endpoint: event.endpoint.as_deref(),
102 message: event.message.as_deref(),
103 error_code: event.error_code,
104 response_time_ms: event.response_time_ms,
105 metadata: event.metadata.to_string(),
106 timestamp: Utc::now(),
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct AnalyticsEvent {
113 pub user_id: UserId,
114 pub session_id: SessionId,
115 pub context_id: ContextId,
116 pub event_type: String,
117 pub event_category: String,
118 pub severity: String,
119 pub endpoint: Option<String>,
120 pub error_code: Option<i32>,
121 pub response_time_ms: Option<i32>,
122 pub agent_id: Option<AgentId>,
123 pub task_id: Option<TaskId>,
124 pub message: Option<String>,
125 pub metadata: Value,
127}