systemprompt-logging 0.2.2

Tracing and audit infrastructure for systemprompt.io AI governance. Structured events, five-point audit traces, and SIEM-ready JSON output — part of the MCP governance pipeline.
Documentation
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use std::sync::Arc;

use super::models::{LevelCount, LogTimeRange, ModuleCount};

struct LevelRow {
    level: String,
    count: Option<i64>,
}

struct ModuleRow {
    module: String,
    count: Option<i64>,
}

struct TimeRangeRow {
    earliest: Option<DateTime<Utc>>,
    latest: Option<DateTime<Utc>>,
}

pub async fn count_logs_by_level(
    pool: &Arc<PgPool>,
    since: Option<DateTime<Utc>>,
) -> Result<Vec<LevelCount>> {
    let rows = sqlx::query_as!(
        LevelRow,
        r#"
        SELECT level as "level!", COUNT(*) as "count"
        FROM logs
        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
        GROUP BY level
        "#,
        since
    )
    .fetch_all(&**pool)
    .await
    .context("Failed to count logs by level")?;

    Ok(rows
        .into_iter()
        .map(|r| LevelCount {
            level: r.level,
            count: r.count.unwrap_or(0),
        })
        .collect())
}

pub async fn top_modules(
    pool: &Arc<PgPool>,
    since: Option<DateTime<Utc>>,
    limit: i64,
) -> Result<Vec<ModuleCount>> {
    let rows = sqlx::query_as!(
        ModuleRow,
        r#"
        SELECT module as "module!", COUNT(*) as "count"
        FROM logs
        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
        GROUP BY module
        ORDER BY count DESC
        LIMIT $2
        "#,
        since,
        limit
    )
    .fetch_all(&**pool)
    .await
    .context("Failed to get top modules")?;

    Ok(rows
        .into_iter()
        .map(|r| ModuleCount {
            module: r.module,
            count: r.count.unwrap_or(0),
        })
        .collect())
}

pub async fn log_time_range(
    pool: &Arc<PgPool>,
    since: Option<DateTime<Utc>>,
) -> Result<LogTimeRange> {
    let row = sqlx::query_as!(
        TimeRangeRow,
        r#"
        SELECT MIN(timestamp) as "earliest", MAX(timestamp) as "latest"
        FROM logs
        WHERE ($1::TIMESTAMPTZ IS NULL OR timestamp >= $1)
        "#,
        since
    )
    .fetch_one(&**pool)
    .await
    .context("Failed to get log time range")?;

    Ok(LogTimeRange {
        earliest: row.earliest,
        latest: row.latest,
    })
}

pub async fn total_log_count(pool: &Arc<PgPool>) -> Result<i64> {
    sqlx::query_scalar!(r#"SELECT COUNT(*) as "count!" FROM logs"#)
        .fetch_one(&**pool)
        .await
        .context("Failed to count total logs")
}