Skip to main content

systemprompt_runtime/trace/repository/
log_summary.rs

1//! Log summary aggregates: level counts, module counts, time range.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use chrono::{DateTime, Utc};
7
8use super::{Result, TraceError, TraceRepository};
9use crate::trace::models::{LevelCount, LogTimeRange, ModuleCount};
10
11struct LevelRow {
12    level: String,
13    count: Option<i64>,
14}
15
16struct ModuleRow {
17    module: String,
18    count: Option<i64>,
19}
20
21struct TimeRangeRow {
22    earliest: Option<DateTime<Utc>>,
23    latest: Option<DateTime<Utc>>,
24}
25
26impl TraceRepository {
27    pub async fn count_logs_by_level(
28        &self,
29        since: Option<DateTime<Utc>>,
30    ) -> Result<Vec<LevelCount>> {
31        let rows = sqlx::query_as!(
32            LevelRow,
33            r#"
34        SELECT level as "level!", COUNT(*) as "count"
35        FROM logs
36        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
37        GROUP BY level
38        "#,
39            since
40        )
41        .fetch_all(&*self.pool)
42        .await?;
43
44        rows.into_iter()
45            .map(|r| -> Result<LevelCount> {
46                Ok(LevelCount {
47                    level: r.level.parse()?,
48                    count: r.count.unwrap_or(0),
49                })
50            })
51            .collect()
52    }
53
54    pub async fn top_modules(
55        &self,
56        since: Option<DateTime<Utc>>,
57        limit: i64,
58    ) -> Result<Vec<ModuleCount>> {
59        let rows = sqlx::query_as!(
60            ModuleRow,
61            r#"
62        SELECT module as "module!", COUNT(*) as "count"
63        FROM logs
64        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
65        GROUP BY module
66        ORDER BY count DESC
67        LIMIT $2
68        "#,
69            since,
70            limit
71        )
72        .fetch_all(&*self.pool)
73        .await?;
74
75        Ok(rows
76            .into_iter()
77            .map(|r| ModuleCount {
78                module: r.module,
79                count: r.count.unwrap_or(0),
80            })
81            .collect())
82    }
83
84    pub async fn log_time_range(&self, since: Option<DateTime<Utc>>) -> Result<LogTimeRange> {
85        let row = sqlx::query_as!(
86            TimeRangeRow,
87            r#"
88        SELECT MIN(timestamp) as "earliest", MAX(timestamp) as "latest"
89        FROM logs
90        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
91        "#,
92            since
93        )
94        .fetch_one(&*self.pool)
95        .await?;
96
97        Ok(LogTimeRange {
98            earliest: row.earliest,
99            latest: row.latest,
100        })
101    }
102
103    pub async fn total_log_count(&self) -> Result<i64> {
104        sqlx::query_scalar!(r#"SELECT COUNT(*) as "count!" FROM logs"#)
105            .fetch_one(&*self.pool)
106            .await
107            .map_err(TraceError::from)
108    }
109}