0a7612efe2
Completes the X1 full sweep started in a8ad0c6 (db.rs). Closes X1 -> [x].
- persistence/mod.rs: debug! on every yaml/CSV/history write -- the
silent-failure-prone disk paths (write_schema, write_table_data incl.
the empty->delete branch, append_history/_failure).
- runtime.rs: debug! on execute_command_typed dispatch (one per executed
command, complements the db.rs executor logs).
- app.rs: debug! on submit (route + submission mode), dispatch_app_command,
and the ADR-0044 diagram-vs-prose render-mode choice.
- dsl/parser.rs: trace! on parse begin/outcome at the parse_command_inner
choke point -- trace, not debug, because the live overlay/completion
re-parse per keystroke (hot path).
- logging.rs: documented level discipline (error/warn/info/debug/trace) so
the convention survives across sessions.
Levels verified end-to-end through the real worker thread + logging::init.
~75 -> 135 tracing sites total. Tests: 2207 pass / 0 fail / 1 ignored.
Clippy clean.
107 lines
4.2 KiB
Rust
107 lines
4.2 KiB
Rust
//! Tracing-based logging setup.
|
|
//!
|
|
//! TUI applications cannot write logs to stdout/stderr without
|
|
//! corrupting the terminal, so logs go to a file. The path comes
|
|
//! from the CLI (`--log-file`) or the `RDBMS_PLAYGROUND_LOG_FILE`
|
|
//! environment variable; if neither is set we default to
|
|
//! `~/.rdbms-playground/playground.log` and create directories as
|
|
//! needed.
|
|
//!
|
|
//! ## Level conventions (X1 — `requirements.md`)
|
|
//!
|
|
//! Instrumentation across the tree follows a consistent level
|
|
//! discipline so the default `info` filter stays quiet and
|
|
//! `RDBMS_PLAYGROUND_LOG=debug` (or `=trace`) is a rich, layered
|
|
//! diagnostic stream. The env filter (`RDBMS_PLAYGROUND_LOG`,
|
|
//! full `EnvFilter` syntax) controls this independently of the
|
|
//! file path above; the default is `info`.
|
|
//!
|
|
//! - **`error!`** — unrecoverable failure (fatal persistence, a
|
|
//! panic-equivalent). The process is going down or a command is
|
|
//! hard-failing.
|
|
//! - **`warn!`** — recoverable failure or a fallback taken (a
|
|
//! snapshot couldn't be staged, a `PRAGMA` couldn't be restored,
|
|
//! an integrity check rolled a rebuild back).
|
|
//! - **`info!`** — low-volume lifecycle, visible by default: db
|
|
//! worker start/exit, project create/open, "logging initialised".
|
|
//! - **`debug!`** — the bulk of instrumentation, one line per
|
|
//! *executed* command and the decision points within it (executor
|
|
//! entry with key params, autofill/cascade summaries, the
|
|
//! rebuild-table primitive, persistence writes, render-mode
|
|
//! choice). Off by default.
|
|
//! - **`trace!`** — hot paths only: per-keystroke parsing
|
|
//! (`dsl::parser`), per-key input handling (`app`), per-refresh
|
|
//! table reads. A firehose; never on except when debugging that
|
|
//! specific layer.
|
|
//!
|
|
//! Rule of thumb for new code: a loop logs a single summary count,
|
|
//! never per-iteration at `debug`/`info`. Logs are developer-facing,
|
|
//! so naming the engine (SQLite/PRAGMA) is fine here even though the
|
|
//! "no engine name" rule (ADR-0002) forbids it in user-facing strings.
|
|
|
|
use std::fs::{File, OpenOptions, create_dir_all};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{Context, Result};
|
|
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
const DEFAULT_LOG_DIR: &str = ".rdbms-playground";
|
|
const DEFAULT_LOG_FILE: &str = "playground.log";
|
|
|
|
/// Initialise tracing to write to the given file path, or to a
|
|
/// platform-default path when `path` is `None`.
|
|
pub fn init(path: Option<&Path>) -> Result<PathBuf> {
|
|
let chosen = match path {
|
|
Some(p) => p.to_path_buf(),
|
|
None => default_log_path()?,
|
|
};
|
|
if let Some(parent) = chosen.parent() {
|
|
create_dir_all(parent)
|
|
.with_context(|| format!("create log directory {}", parent.display()))?;
|
|
}
|
|
let file = open_log_file(&chosen)?;
|
|
let filter = EnvFilter::try_from_env("RDBMS_PLAYGROUND_LOG")
|
|
.unwrap_or_else(|_| EnvFilter::new("info"));
|
|
let layer = fmt::layer()
|
|
.with_writer(file)
|
|
.with_ansi(false)
|
|
.with_target(true);
|
|
tracing_subscriber::registry()
|
|
.with(filter)
|
|
.with(layer)
|
|
.init();
|
|
tracing::info!(path = %chosen.display(), "logging initialised");
|
|
Ok(chosen)
|
|
}
|
|
|
|
fn open_log_file(path: &Path) -> Result<File> {
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(path)
|
|
.with_context(|| format!("open log file {}", path.display()))
|
|
}
|
|
|
|
fn default_log_path() -> Result<PathBuf> {
|
|
let home = home_dir().context("could not determine HOME directory for default log path")?;
|
|
Ok(home.join(DEFAULT_LOG_DIR).join(DEFAULT_LOG_FILE))
|
|
}
|
|
|
|
fn home_dir() -> Option<PathBuf> {
|
|
// std::env::home_dir is deprecated; do the lookup ourselves with
|
|
// the platform-conventional environment variables. Once we add a
|
|
// proper path strategy (project storage, ADR-0004) this can be
|
|
// replaced with the chosen helper crate.
|
|
if let Some(p) = std::env::var_os("HOME") {
|
|
return Some(PathBuf::from(p));
|
|
}
|
|
if let (Some(drive), Some(path)) =
|
|
(std::env::var_os("HOMEDRIVE"), std::env::var_os("HOMEPATH"))
|
|
{
|
|
let mut combined = PathBuf::from(drive);
|
|
combined.push(path);
|
|
return Some(combined);
|
|
}
|
|
std::env::var_os("USERPROFILE").map(PathBuf::from)
|
|
}
|