Skip to main content

systemprompt_runtime/trace/repository/
log_summary.rs

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