systemprompt-runtime 0.65.0

Application runtime for systemprompt.io AI governance infrastructure. AppContext, lifecycle builder, extension registry, and module wiring for the MCP governance pipeline.
Documentation
//! Full-text log search queries for trace tooling.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use chrono::{DateTime, Utc};

use systemprompt_identifiers::{LogId, McpServerId, McpToolName, TraceId};
use systemprompt_logging::LogLevel;

use super::{Result, TraceRepository};
use crate::trace::models::{LogSearchItem, ToolExecutionItem};

struct LogRow {
    id: LogId,
    trace_id: TraceId,
    timestamp: DateTime<Utc>,
    level: String,
    module: String,
    message: String,
    metadata: Option<String>,
}

impl TraceRepository {
    pub async fn search_logs(
        &self,
        pattern: &str,
        since: Option<DateTime<Utc>>,
        level: Option<LogLevel>,
        limit: i64,
    ) -> Result<Vec<LogSearchItem>> {
        let rows = sqlx::query_as!(
            LogRow,
            r#"
        SELECT
            id as "id!: LogId",
            trace_id as "trace_id!: TraceId",
            timestamp as "timestamp!",
            level as "level!",
            module as "module!",
            message as "message!",
            metadata
        FROM logs
        WHERE message ILIKE $1
          AND ($2::timestamptz IS NULL OR timestamp >= $2)
          AND ($3::text IS NULL OR UPPER(level) = $3)
        ORDER BY timestamp DESC
        LIMIT $4
        "#,
            pattern,
            since,
            level.map(LogLevel::as_str),
            limit
        )
        .fetch_all(&*self.pool)
        .await?;

        rows.into_iter()
            .map(|r| -> Result<LogSearchItem> {
                Ok(LogSearchItem {
                    id: r.id,
                    trace_id: r.trace_id,
                    timestamp: r.timestamp,
                    level: r.level.parse()?,
                    module: r.module,
                    message: r.message,
                    metadata: r.metadata,
                })
            })
            .collect()
    }

    pub async fn search_tool_executions(
        &self,
        pattern: &str,
        since: Option<DateTime<Utc>>,
        limit: i64,
    ) -> Result<Vec<ToolExecutionItem>> {
        let rows = sqlx::query!(
            r#"
        SELECT
            started_at as "timestamp!",
            trace_id as "trace_id!: TraceId",
            tool_name as "tool_name!",
            server_name,
            status as "status!",
            execution_time_ms
        FROM mcp_tool_executions
        WHERE (tool_name ILIKE $1 OR server_name ILIKE $1)
          AND ($2::timestamptz IS NULL OR started_at >= $2)
        ORDER BY started_at DESC
        LIMIT $3
        "#,
            pattern,
            since,
            limit
        )
        .fetch_all(&*self.pool)
        .await?;

        Ok(rows
            .into_iter()
            .map(|r| ToolExecutionItem {
                timestamp: r.timestamp,
                trace_id: r.trace_id,
                tool_name: McpToolName::new(r.tool_name),
                server_name: Some(McpServerId::new(r.server_name)),
                status: r.status,
                execution_time_ms: r.execution_time_ms,
            })
            .collect())
    }
}