feat: first-draft preview-capable zosstorage
- CLI: add topology selection (-t/--topology), preview flags (--show/--report), and removable policy override (--allow-removable) (src/cli/args.rs) - Config: built-in sensible defaults; deterministic overlays for logging, fstab, removable, topology (src/config/loader.rs) - Device: discovery via /proc + /sys with include/exclude regex and removable policy (src/device/discovery.rs) - Idempotency: detection via blkid; safe emptiness checks (src/idempotency/mod.rs) - Partition: topology-driven planning (Single, DualIndependent, BtrfsRaid1, SsdHddBcachefs) (src/partition/plan.rs) - FS: planning + creation (mkfs.vfat, mkfs.btrfs, bcachefs format) and UUID capture via blkid (src/fs/plan.rs) - Orchestrator: pre-flight with preview JSON (disks, partition_plan, filesystems_planned, mount scheme). Skips emptiness in preview; supports stdout+file (src/orchestrator/run.rs) - Util/Logging/Types/Errors: process execution, tracing, shared types (src/util/mod.rs, src/logging/mod.rs, src/types.rs, src/errors.rs) - Docs: add README with exhaustive usage and preview JSON shape (README.md) Builds and unit tests pass: discovery, util, idempotency helpers, and fs parser tests.
This commit is contained in:
136
src/logging/mod.rs
Normal file
136
src/logging/mod.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// REGION: API
|
||||
// api: logging::LogOptions { level: String, to_file: bool }
|
||||
// api: logging::LogOptions::from_cli(cli: &crate::cli::Cli) -> Self
|
||||
// api: logging::init_logging(opts: &LogOptions) -> crate::Result<()>
|
||||
// REGION: API-END
|
||||
//
|
||||
// REGION: RESPONSIBILITIES
|
||||
// - Provide structured logging initialization via tracing, defaulting to stderr.
|
||||
// - Optionally enable file logging at /run/zosstorage/zosstorage.log.
|
||||
// Non-goals: runtime log level reconfiguration or external log forwarders.
|
||||
// REGION: RESPONSIBILITIES-END
|
||||
//
|
||||
// REGION: EXTENSION_POINTS
|
||||
// ext: add env-filter support for selective module verbosity (feature-gated).
|
||||
// ext: add JSON log formatting for machine-readability (feature-gated).
|
||||
// REGION: EXTENSION_POINTS-END
|
||||
//
|
||||
// REGION: SAFETY
|
||||
// safety: initialization must be idempotent; calling twice should not double-install layers.
|
||||
// REGION: SAFETY-END
|
||||
//
|
||||
// REGION: ERROR_MAPPING
|
||||
// errmap: IO errors when opening log file -> crate::Error::Other(anyhow)
|
||||
// REGION: ERROR_MAPPING-END
|
||||
//
|
||||
// REGION: TODO
|
||||
// todo: implement file layer initialization and idempotent guard.
|
||||
// REGION: TODO-END
|
||||
//! Logging initialization and options for zosstorage.
|
||||
//!
|
||||
//! Provides structured logging via the `tracing` ecosystem. Defaults to stderr,
|
||||
//! with an optional file target at /run/zosstorage/zosstorage.log.
|
||||
|
||||
use crate::Result;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{self};
|
||||
use std::sync::OnceLock;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::fmt;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::registry::Registry;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
/// Logging options resolved from CLI and/or config.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogOptions {
|
||||
/// Level: "error" | "warn" | "info" | "debug"
|
||||
pub level: String,
|
||||
/// When true, also log to /run/zosstorage/zosstorage.log
|
||||
pub to_file: bool,
|
||||
}
|
||||
|
||||
impl LogOptions {
|
||||
/// Construct options from [struct Cli](src/cli/mod.rs:1).
|
||||
pub fn from_cli(cli: &crate::cli::Cli) -> Self {
|
||||
Self {
|
||||
level: cli.log_level.to_string(),
|
||||
to_file: cli.log_to_file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn level_from_str(s: &str) -> Level {
|
||||
match s {
|
||||
"error" => Level::ERROR,
|
||||
"warn" => Level::WARN,
|
||||
"info" => Level::INFO,
|
||||
"debug" => Level::DEBUG,
|
||||
_ => Level::INFO,
|
||||
}
|
||||
}
|
||||
|
||||
static INIT_GUARD: OnceLock<()> = OnceLock::new();
|
||||
|
||||
/// Initialize tracing subscriber according to options.
|
||||
/// Must be idempotent when called once in process lifetime.
|
||||
pub fn init_logging(opts: &LogOptions) -> Result<()> {
|
||||
if INIT_GUARD.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let lvl = level_from_str(&opts.level);
|
||||
let stderr_layer = fmt::layer()
|
||||
.with_writer(io::stderr) // no timestamps by default for initramfs
|
||||
.with_ansi(false)
|
||||
.with_level(true)
|
||||
.with_target(false)
|
||||
.with_thread_ids(false)
|
||||
.with_thread_names(false)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_filter(LevelFilter::from_level(lvl));
|
||||
|
||||
if opts.to_file {
|
||||
let log_path = "/run/zosstorage/zosstorage.log";
|
||||
if let Ok(file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(log_path)
|
||||
{
|
||||
// Make a writer that clones the file handle per write to satisfy MakeWriter.
|
||||
let make_file = move || file.try_clone().expect("failed to clone log file handle");
|
||||
let file_layer = fmt::layer()
|
||||
.with_writer(make_file)
|
||||
.with_ansi(false)
|
||||
.with_level(true)
|
||||
.with_target(false)
|
||||
.with_thread_ids(false)
|
||||
.with_thread_names(false)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_filter(LevelFilter::from_level(lvl));
|
||||
Registry::default()
|
||||
.with(stderr_layer)
|
||||
.with(file_layer)
|
||||
.try_init()
|
||||
.map_err(|e| crate::Error::Other(anyhow::anyhow!("failed to set global logger: {}", e)))?;
|
||||
} else {
|
||||
// Fall back to stderr-only if file cannot be opened
|
||||
Registry::default()
|
||||
.with(stderr_layer)
|
||||
.try_init()
|
||||
.map_err(|e| crate::Error::Other(anyhow::anyhow!("failed to set global logger: {}", e)))?;
|
||||
}
|
||||
} else {
|
||||
Registry::default()
|
||||
.with(stderr_layer)
|
||||
.try_init()
|
||||
.map_err(|e| crate::Error::Other(anyhow::anyhow!("failed to set global logger: {}", e)))?;
|
||||
}
|
||||
|
||||
let _ = INIT_GUARD.set(());
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user