From a6509d8b30cc5e42c44d54e83d6f74a97d1c41b0 Mon Sep 17 00:00:00 2001 From: rafapolo Date: Sat, 28 Mar 2026 12:17:34 +0100 Subject: [PATCH] Add logging to ask app: save questions, SQLs, success/error status, and timestamps to logs/log.json --- ask/Cargo.lock | 3 +++ ask/Cargo.toml | 1 + ask/src/main.rs | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/ask/Cargo.lock b/ask/Cargo.lock index b27dced..7b6340a 100644 --- a/ask/Cargo.lock +++ b/ask/Cargo.lock @@ -246,6 +246,7 @@ name = "ask" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "crossterm", "dotenvy", "duckdb", @@ -420,7 +421,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-link", ] diff --git a/ask/Cargo.toml b/ask/Cargo.toml index 19c0868..d017feb 100644 --- a/ask/Cargo.toml +++ b/ask/Cargo.toml @@ -13,6 +13,7 @@ serde_json = "1" duckdb = { version = "1", features = ["bundled"] } dotenvy = "0.15" anyhow = "1" +chrono = "0.4" syntect = "5" ratatui = "0.29" crossterm = { version = "0.28", features = ["event-stream"] } diff --git a/ask/src/main.rs b/ask/src/main.rs index 8c9c460..c3f52c5 100644 --- a/ask/src/main.rs +++ b/ask/src/main.rs @@ -16,6 +16,7 @@ use ratatui::{ widgets::{Block, Borders, Gauge, Paragraph, Row, Table, TableState, Wrap}, Frame, Terminal, }; +use chrono::Utc; use serde_json::{json, Value}; use std::{ env, fs, @@ -125,6 +126,35 @@ fn fmt_timer(d: Duration) -> String { format!("{:02}:{:02}s", d.as_secs() / 60, d.as_secs() % 60) } +fn log_question(question: &str, sql: &str, success: bool, error: Option<&str>) { + let log_dir = std::path::Path::new("logs"); + if !log_dir.exists() { + std::fs::create_dir_all(log_dir).ok(); + } + let log_file = log_dir.join("log.json"); + + let timestamp = Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string(); + + let entry = serde_json::json!({ + "timestamp": timestamp, + "question": question, + "sql": sql, + "success": success, + "error": error, + }); + + let mut entries: Vec = if log_file.exists() { + let content = std::fs::read_to_string(&log_file).unwrap_or_else(|_| "[]".to_string()); + serde_json::from_str(&content).unwrap_or_else(|_| vec![]) + } else { + vec![] + }; + + entries.push(entry); + let json = serde_json::to_string_pretty(&entries).unwrap_or_else(|_| "[]".to_string()); + std::fs::write(&log_file, json).ok(); +} + fn wrap_text(text: &str, max_width: usize) -> Vec { if max_width == 0 { return vec![text.to_string()]; @@ -209,16 +239,21 @@ fn spawn_worker( std::thread::spawn( move || match ask_model(&question, &schema, &model, &prompt_file) { Err(e) => { - tx.send(WorkerMsg::SqlError(format!("{:#}", e))).ok(); + let err = format!("{:#}", e); + log_question(&question, "", false, Some(&err)); + tx.send(WorkerMsg::SqlError(err)).ok(); } Ok(sql) => { tx.send(WorkerMsg::SqlReady(sql.clone())).ok(); match run_query(&db_file, &sql) { Ok((cols, rows)) => { + log_question(&question, &sql, true, None); tx.send(WorkerMsg::QueryOk(cols, rows)).ok(); } Err(e) => { - tx.send(WorkerMsg::QueryError(format!("{:#}", e))).ok(); + let err = format!("{:#}", e); + log_question(&question, &sql, false, Some(&err)); + tx.send(WorkerMsg::QueryError(err)).ok(); } } }