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:
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,
- })
}