Files
rdbms-playground/src/logging.rs
T
claude@clouddev1 25a0f1260f TUI walking skeleton (Phase 4)
First implementation milestone: Cargo project, dependencies,
and a minimal but functional TUI shell built on Ratatui +
Crossterm + Tokio in the Elm-style update/view pattern
(Candidate A from Phase 2/3 selection).

Includes:
- Three-region layout: items list (left), output + input + hint
  (right), bottom status bar with mode-aware shortcuts.
- Two themes (light, dark) plus COLORFGBG auto-detect, per
  NFR-7. CLI: --theme {light,dark}, --log-file <path>.
- Input modes per ADR-0003: simple (default), advanced, with
  the `:` one-shot escape including immediate prompt reaction
  ("Advanced:" label, advanced border) and auto-inserted space
  after a leading `:` in simple mode.
- App-level commands: `quit`/`q`, `mode simple`/`mode advanced`
  (canonical list per ADR-0003 — remaining commands land in
  later iterations).
- File logging via tracing, defaulting to ~/.rdbms-playground/
  playground.log so the TUI is not corrupted by stdio.

Testing per ADR-0008:
- Tier 1: 29 unit tests covering input handling, mode switch,
  one-shot escape, auto-space, output buffering, CLI parsing.
- Tier 2: 4 insta snapshots (default simple/advanced/light,
  one-shot active) of TestBackend frames.
- Tier 3: 7 integration tests driving synthetic events through
  App::update + render path.

All green: 36 tests, 0 failures, 0 skips. Clippy clean with
nursery lints enabled.
2026-05-07 11:17:58 +00:00

75 lines
2.6 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.
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)
}