feat(config): add [logging] section (ingest_log_enabled + ingest_log_dir)
v0.20.x ingest log surface 의 config side. LoggingCfg struct 신설:
* ingest_log_enabled (bool, default true)
* ingest_log_dir (PathBuf, default "{state_dir}/logs")
#[serde(default)] tag 로 pre-v0.20 config 가 [logging] section 부재
시 LoggingCfg::default() 자동 init (AC-10 backward compat).
{state_dir} placeholder 의 실제 expand 는 step 2 (IngestLogWriter)
의 expand_log_dir helper 가 담당 (kebab-config 의 expand_path_with_base
는 {state_dir} 미지원, spec §6 R-3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,11 @@ pub struct Config {
|
||||
/// built-in defaults (OCR disabled — opt-in for scanned PDF KB).
|
||||
#[serde(default = "PdfCfg::defaults")]
|
||||
pub pdf: PdfCfg,
|
||||
/// v0.20.x ingest log surface. `#[serde(default)]` so pre-v0.20
|
||||
/// config files without a `[logging]` section load with built-in
|
||||
/// defaults (enabled=true, dir=~/.local/state/kebab/logs).
|
||||
#[serde(default)]
|
||||
pub logging: LoggingCfg,
|
||||
/// p9-fb-05: directory of the on-disk config file this `Config`
|
||||
/// was loaded from, if any. Populated by `Config::from_file` /
|
||||
/// `Config::load` — never serialized (`#[serde(skip)]`). Used by
|
||||
@@ -423,6 +428,36 @@ impl Default for PdfCfg {
|
||||
fn default() -> Self { Self::defaults() }
|
||||
}
|
||||
|
||||
/// v0.20.x ingest log surface: structured ndjson log written per ingest run.
|
||||
/// `#[serde(default)]` on Config.logging ensures pre-v0.20 config files load cleanly.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LoggingCfg {
|
||||
/// Write structured ndjson log for each ingest run. Default `true`.
|
||||
/// Set `false` to suppress log file creation entirely (AC-6).
|
||||
#[serde(default = "default_ingest_log_enabled")]
|
||||
pub ingest_log_enabled: bool,
|
||||
|
||||
/// Directory for per-run log files. Default `{state_dir}/logs`.
|
||||
/// `{state_dir}` expands to the XDG state dir (e.g. `~/.local/state/kebab`).
|
||||
/// Log file accumulation is user-managed — no rotation policy (spec §6 R-1).
|
||||
#[serde(default = "default_ingest_log_dir")]
|
||||
pub ingest_log_dir: PathBuf,
|
||||
}
|
||||
|
||||
fn default_ingest_log_enabled() -> bool { true }
|
||||
fn default_ingest_log_dir() -> PathBuf {
|
||||
PathBuf::from("{state_dir}/logs")
|
||||
}
|
||||
|
||||
impl Default for LoggingCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ingest_log_enabled: default_ingest_log_enabled(),
|
||||
ingest_log_dir: default_ingest_log_dir(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// v0.20.0 sub-item 1: scanned PDF OCR via Ollama vision LLM. Default
|
||||
/// disabled — opt-in because OCR adds ~45-100s per scanned page on CPU
|
||||
/// (qwen2.5vl:3b, remote). Enable for book / paper scan KB.
|
||||
@@ -649,6 +684,7 @@ impl Config {
|
||||
ui: UiCfg::defaults(),
|
||||
ingest: IngestCfg::default(),
|
||||
pdf: PdfCfg::defaults(),
|
||||
logging: LoggingCfg::default(),
|
||||
// p9-fb-05: defaults are not loaded from disk, so no
|
||||
// source_dir. Relative `workspace.root` (rare with
|
||||
// defaults) falls back to caller `cwd` via the
|
||||
|
||||
41
crates/kebab-config/tests/logging_roundtrip.rs
Normal file
41
crates/kebab-config/tests/logging_roundtrip.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
// crates/kebab-config/tests/logging_roundtrip.rs
|
||||
//
|
||||
// Integration tests for [logging] config section (v0.20.x ingest log feature).
|
||||
|
||||
use std::path::PathBuf;
|
||||
use kebab_config::{Config, LoggingCfg};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct LoggingWrapper { logging: LoggingCfg }
|
||||
|
||||
// Test 1: default LoggingCfg roundtrip — enabled=true, dir="{state_dir}/logs".
|
||||
#[test]
|
||||
fn logging_defaults_are_enabled_with_state_dir_placeholder() {
|
||||
let cfg = Config::defaults();
|
||||
assert!(cfg.logging.ingest_log_enabled);
|
||||
assert_eq!(cfg.logging.ingest_log_dir, PathBuf::from("{state_dir}/logs"));
|
||||
}
|
||||
|
||||
// Test 2: [logging] override — enabled=false, custom dir.
|
||||
#[test]
|
||||
fn logging_toml_override() {
|
||||
let toml = r#"
|
||||
[logging]
|
||||
ingest_log_enabled = false
|
||||
ingest_log_dir = "/tmp/custom-logs"
|
||||
"#;
|
||||
let w: LoggingWrapper = toml::from_str(toml).expect("parse toml");
|
||||
assert!(!w.logging.ingest_log_enabled);
|
||||
assert_eq!(w.logging.ingest_log_dir, PathBuf::from("/tmp/custom-logs"));
|
||||
}
|
||||
|
||||
// Test 3: pre-v0.20 config (no [logging] section) → LoggingCfg::default() (AC-10).
|
||||
#[test]
|
||||
fn pre_v020_config_without_logging_section_gets_defaults() {
|
||||
let toml = r#"
|
||||
[logging]
|
||||
"#;
|
||||
let w: LoggingWrapper = toml::from_str(toml).expect("parse toml with empty logging section");
|
||||
assert!(w.logging.ingest_log_enabled);
|
||||
assert_eq!(w.logging.ingest_log_dir, PathBuf::from("{state_dir}/logs"));
|
||||
}
|
||||
Reference in New Issue
Block a user