lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit d586c196ba8d00dc501a37e55210e385865763a1
parent 33b566cecf71ce0e6f2a122c52779b272797e920
Author: triesap <tyson@radroots.org>
Date:   Fri,  2 Jan 2026 15:12:51 +0000

log: centralize tracing init and improve error handling


- Replace local subscriber setup with `radroots-log` init
- Add default_level to logging options and env fallback
- Simplify init paths by using io/try_init error conversions
- Drop direct tracing-appender/tracing-subscriber deps from crates

Diffstat:
MCargo.lock | 5+----
Mlog/src/error.rs | 11+++++++----
Mlog/src/init.rs | 42+++++++++++++++++++++---------------------
Mlog/src/options.rs | 2++
Mnet-core/Cargo.toml | 4+---
Mnet-core/src/logging.rs | 66+++++++++++++++++-------------------------------------------------
Mruntime/Cargo.toml | 3+--
Mruntime/src/error.rs | 11++---------
Mruntime/src/tracing.rs | 79++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
9 files changed, 98 insertions(+), 125 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1805,8 +1805,6 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tracing", - "tracing-appender", - "tracing-subscriber", ] [[package]] @@ -1832,6 +1830,7 @@ dependencies = [ "anyhow", "clap", "config", + "radroots-log", "serde", "serde_json", "tempfile", @@ -1839,8 +1838,6 @@ dependencies = [ "tokio", "toml", "tracing", - "tracing-appender", - "tracing-subscriber", ] [[package]] diff --git a/log/src/error.rs b/log/src/error.rs @@ -6,13 +6,16 @@ use thiserror::Error; #[cfg_attr(feature = "std", derive(Error))] #[derive(Debug)] pub enum Error { - #[cfg(feature = "std")] - #[error("{0}")] + #[cfg_attr(feature = "std", error("{0}"))] Msg(String), #[cfg(feature = "std")] - #[error("logging init failed: {0}")] - Init(&'static str), + #[error(transparent)] + Init(#[from] tracing_subscriber::util::TryInitError), + + #[cfg(feature = "std")] + #[error(transparent)] + Io(#[from] std::io::Error), } pub type Result<T> = core::result::Result<T, Error>; diff --git a/log/src/init.rs b/log/src/init.rs @@ -1,12 +1,12 @@ use std::fs; use std::sync::OnceLock; -use tracing::{Level, info}; +use tracing::info; use tracing_subscriber::prelude::*; use tracing_subscriber::{EnvFilter, fmt}; use crate::options::LoggingOptions; -use crate::{Error, Result}; +use crate::Result; static GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new(); static INIT: OnceLock<()> = OnceLock::new(); @@ -17,7 +17,7 @@ pub fn init_logging(opts: LoggingOptions) -> Result<()> { } let writer = if let Some(dir) = &opts.dir { - fs::create_dir_all(dir).map_err(|_| Error::Init("mkdir"))?; + fs::create_dir_all(dir)?; let file_appender = tracing_appender::rolling::daily(dir, &opts.file_name); let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); let _ = GUARD.set(guard); @@ -26,10 +26,13 @@ pub fn init_logging(opts: LoggingOptions) -> Result<()> { None }; - let env = EnvFilter::from_default_env().add_directive(Level::INFO.into()); - let fmt_layer_file = writer.as_ref().map(|w| fmt::layer().with_writer(w.clone())); + let env = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new(opts.default_level.as_deref().unwrap_or("info"))); + let fmt_layer_file = writer + .as_ref() + .map(|w| fmt::layer().with_writer(w.clone()).with_ansi(false).with_target(false)); let fmt_layer_stdout = if opts.also_stdout() { - Some(fmt::layer()) + Some(fmt::layer().with_writer(std::io::stdout).with_target(false)) } else { None }; @@ -39,21 +42,17 @@ pub fn init_logging(opts: LoggingOptions) -> Result<()> { .with(fmt_layer_file) .with(fmt_layer_stdout); - match subscriber.try_init() { - Ok(()) => { - let _ = INIT.set(()); - info!( - "logging initialized (file: {}, stdout: {})", - opts.dir - .as_ref() - .map(|d| d.join(&opts.file_name).display().to_string()) - .unwrap_or_else(|| "<disabled>".into()), - opts.also_stdout() - ); - Ok(()) - } - Err(_) => Ok(()), - } + subscriber.try_init()?; + let _ = INIT.set(()); + info!( + "logging initialized (file: {}, stdout: {})", + opts.dir + .as_ref() + .map(|d| d.join(&opts.file_name).display().to_string()) + .unwrap_or_else(|| "<disabled>".into()), + opts.also_stdout() + ); + Ok(()) } pub fn init_stdout() -> Result<()> { @@ -61,5 +60,6 @@ pub fn init_stdout() -> Result<()> { dir: None, file_name: "radroots.log".into(), stdout: true, + default_level: None, }) } diff --git a/log/src/options.rs b/log/src/options.rs @@ -5,6 +5,7 @@ pub struct LoggingOptions { pub dir: Option<PathBuf>, pub file_name: String, pub stdout: bool, + pub default_level: Option<String>, } impl LoggingOptions { @@ -19,6 +20,7 @@ impl Default for LoggingOptions { dir: None, file_name: "radroots.log".into(), stdout: true, + default_level: None, } } } diff --git a/net-core/Cargo.toml b/net-core/Cargo.toml @@ -25,7 +25,7 @@ fs-persistence = ["std"] [dependencies] radroots-events = { workspace = true, optional = true, default-features = true, features = ["std", "serde", "typeshare"] } -radroots-log = { workspace = true } +radroots-log = { workspace = true, features = ["std"] } radroots-nostr = { workspace = true, optional = true, default-features = true, features = ["client", "events"] } directories = { workspace = true, optional = true } hex = { workspace = true, optional = true } @@ -36,6 +36,4 @@ tempfile = { workspace = true, optional = true } thiserror = { workspace = true } tokio = { workspace = true, optional = true, features = ["rt-multi-thread"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } -tracing-appender = { workspace = true } futures = { workspace = true } diff --git a/net-core/src/logging.rs b/net-core/src/logging.rs @@ -1,9 +1,6 @@ use crate::error::{NetError, Result}; -use std::sync::OnceLock; -use std::{fs, path::PathBuf}; -use tracing::{Level, info}; -use tracing_subscriber::prelude::*; -use tracing_subscriber::{EnvFilter, fmt}; +use std::path::PathBuf; +use tracing::info; #[derive(Debug, Clone)] pub struct LoggingOptions { @@ -22,50 +19,21 @@ impl Default for LoggingOptions { } } -static GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new(); -static INIT: OnceLock<()> = OnceLock::new(); - pub fn init_logging(opts: LoggingOptions) -> Result<()> { - if INIT.get().is_some() { - return Ok(()); - } - - let writer = if let Some(dir) = &opts.dir { - fs::create_dir_all(dir).map_err(|_| NetError::LoggingInit("mkdir"))?; - let file_appender = tracing_appender::rolling::daily(dir, &opts.file_name); - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - let _ = GUARD.set(guard); - Some(non_blocking) - } else { - None - }; - - let env = EnvFilter::from_default_env().add_directive(Level::INFO.into()); - let fmt_layer_file = writer.as_ref().map(|w| fmt::layer().with_writer(w.clone())); - let fmt_layer_stdout = if opts.also_stdout { - Some(fmt::layer()) - } else { - None + let log_opts = radroots_log::LoggingOptions { + dir: opts.dir.clone(), + file_name: opts.file_name.clone(), + stdout: opts.also_stdout, + default_level: None, }; - - let subscriber = tracing_subscriber::registry() - .with(env) - .with(fmt_layer_file) - .with(fmt_layer_stdout); - - match subscriber.try_init() { - Ok(()) => { - let _ = INIT.set(()); - info!( - "logging initialized (file: {}, stdout: {})", - opts.dir - .as_ref() - .map(|d| d.join(&opts.file_name).display().to_string()) - .unwrap_or_else(|| "<disabled>".into()), - opts.also_stdout - ); - Ok(()) - } - Err(_) => Ok(()), - } + radroots_log::init_logging(log_opts).map_err(|_| NetError::LoggingInit("init"))?; + info!( + "logging initialized (file: {}, stdout: {})", + opts.dir + .as_ref() + .map(|d| d.join(&opts.file_name).display().to_string()) + .unwrap_or_else(|| "<disabled>".into()), + opts.also_stdout + ); + Ok(()) } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml @@ -14,12 +14,11 @@ cli = ["dep:clap"] anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"], optional = true } config = { workspace = true } +radroots-log = { workspace = true, features = ["std"] } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } toml = { workspace = true } tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } -tracing-appender = { workspace = true } tempfile = { workspace = true } diff --git a/runtime/src/error.rs b/runtime/src/error.rs @@ -19,15 +19,8 @@ pub enum RuntimeCliError { #[derive(Debug, Error)] pub enum RuntimeTracingError { - #[error("failed to initialize tracing subscriber: {0}")] - Init(#[from] tracing_subscriber::util::TryInitError), - - #[error("failed to create logs directory at {path}: {source}")] - CreateLogsDir { - path: std::path::PathBuf, - #[source] - source: std::io::Error, - }, + #[error(transparent)] + Log(#[from] radroots_log::Error), } #[derive(Debug, Error)] diff --git a/runtime/src/tracing.rs b/runtime/src/tracing.rs @@ -1,8 +1,5 @@ -use std::fs; use std::path::{Path, PathBuf}; -use tracing_appender::rolling; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{EnvFilter, Registry, fmt, prelude::*}; +use radroots_log::LoggingOptions; use crate::error::RuntimeTracingError; @@ -15,37 +12,53 @@ pub fn init_with( default_level: Option<&str>, ) -> Result<(), RuntimeTracingError> { let logs_dir = logs_dir.as_ref(); - ensure_dir(logs_dir)?; - - let file_appender = rolling::daily(logs_dir, concat!(env!("CARGO_PKG_NAME"), ".log")); - let (file_writer, guard) = tracing_appender::non_blocking(file_appender); - std::mem::forget(guard); - - let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_target(false); - - let file_layer = fmt::layer() - .with_writer(file_writer) - .with_ansi(false) - .with_target(false); - - let filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new(default_level.unwrap_or("info"))); - - Registry::default() - .with(filter) - .with(stdout_layer) - .with(file_layer) - .try_init()?; - + let env_dir = std::env::var("RADROOTS_LOG_DIR") + .ok() + .and_then(|value| { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(PathBuf::from(trimmed)) + } + }); + let env_file = std::env::var("RADROOTS_LOG_FILE") + .ok() + .and_then(|value| { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } + }); + let dir = env_dir.or_else(|| { + if logs_dir.as_os_str().is_empty() { + None + } else { + Some(logs_dir.to_path_buf()) + } + }); + let opts = LoggingOptions { + dir, + file_name: env_file.unwrap_or_else(default_log_file_name), + stdout: true, + default_level: default_level.map(str::to_string), + }; + radroots_log::init_logging(opts)?; Ok(()) } -fn ensure_dir(dir: &Path) -> Result<(), RuntimeTracingError> { - if dir.exists() { - return Ok(()); +fn default_log_file_name() -> String { + log_name_from_exe().unwrap_or_else(|| format!("{}.log", env!("CARGO_PKG_NAME"))) +} + +fn log_name_from_exe() -> Option<String> { + let exe = std::env::current_exe().ok()?; + let name = exe.file_stem()?.to_string_lossy(); + if name.is_empty() { + None + } else { + Some(format!("{name}.log")) } - fs::create_dir_all(dir).map_err(|source| RuntimeTracingError::CreateLogsDir { - path: PathBuf::from(dir), - source, - }) }