tracing.rs (3975B)
1 use radroots_log::{LogFileLayout, LoggingOptions}; 2 use radroots_runtime_paths::{ 3 RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimePathsError, 4 default_shared_runtime_logs_dir as resolve_shared_runtime_logs_dir, 5 }; 6 use std::path::{Path, PathBuf}; 7 8 use crate::error::RuntimeTracingError; 9 10 pub fn init() -> Result<(), RuntimeTracingError> { 11 let logs_dir = default_shared_runtime_logs_dir()?; 12 init_with_logs_dir(logs_dir, None) 13 } 14 15 pub fn default_shared_runtime_logs_dir() -> Result<PathBuf, RadrootsRuntimePathsError> { 16 default_shared_runtime_logs_dir_for( 17 ¤t_path_resolver(), 18 RadrootsPathProfile::InteractiveUser, 19 &RadrootsPathOverrides::default(), 20 ) 21 } 22 23 pub fn default_shared_runtime_logs_dir_for( 24 resolver: &RadrootsPathResolver, 25 profile: RadrootsPathProfile, 26 overrides: &RadrootsPathOverrides, 27 ) -> Result<PathBuf, RadrootsRuntimePathsError> { 28 resolve_shared_runtime_logs_dir(resolver, profile, overrides) 29 } 30 31 pub fn init_with_logs_dir( 32 logs_dir: impl AsRef<Path>, 33 default_level: Option<&str>, 34 ) -> Result<(), RuntimeTracingError> { 35 let logs_dir = logs_dir.as_ref(); 36 let env_file = env_value("LOG_FILE").or_else(|| env_value("RADROOTS_LOG_FILE")); 37 let env_level = env_value("LOG_LEVEL").or_else(|| env_value("RUST_LOG")); 38 let opts = LoggingOptions { 39 dir: Some(logs_dir.to_path_buf()), 40 file_name: env_file.unwrap_or_else(default_log_file_name), 41 stdout: true, 42 default_level: resolve_default_level(env_level, default_level), 43 file_layout: LogFileLayout::PrefixedDate, 44 }; 45 radroots_log::init_logging(opts)?; 46 Ok(()) 47 } 48 49 fn default_log_file_name() -> String { 50 default_log_file_name_from_exe_name(log_name_from_exe()) 51 } 52 53 fn default_log_file_name_from_exe_name(exe_name: Option<String>) -> String { 54 exe_name.unwrap_or_else(|| format!("{}.log", env!("CARGO_PKG_NAME"))) 55 } 56 57 fn log_name_from_exe() -> Option<String> { 58 log_name_from_path(std::env::current_exe().ok()) 59 } 60 61 fn log_name_from_path(exe: Option<PathBuf>) -> Option<String> { 62 let exe = exe?; 63 let name = exe.file_stem()?.to_string_lossy(); 64 log_name_from_stem(name.as_ref()) 65 } 66 67 #[cfg(test)] 68 mod test_hooks { 69 use std::cell::{Cell, RefCell}; 70 71 use radroots_runtime_paths::RadrootsPathResolver; 72 73 thread_local! { 74 static IGNORE_ENV: Cell<bool> = const { Cell::new(false) }; 75 static CURRENT_RESOLVER: RefCell<Option<RadrootsPathResolver>> = RefCell::new(None); 76 } 77 78 pub fn set_ignore_env(ignore: bool) { 79 IGNORE_ENV.with(|state| state.set(ignore)); 80 } 81 82 pub fn ignore_env() -> bool { 83 IGNORE_ENV.with(Cell::get) 84 } 85 86 pub fn set_current_resolver(resolver: Option<RadrootsPathResolver>) { 87 CURRENT_RESOLVER.with(|state| *state.borrow_mut() = resolver); 88 } 89 90 pub fn current_resolver() -> Option<RadrootsPathResolver> { 91 CURRENT_RESOLVER.with(|state| state.borrow().clone()) 92 } 93 } 94 95 fn current_path_resolver() -> RadrootsPathResolver { 96 #[cfg(test)] 97 if let Some(resolver) = test_hooks::current_resolver() { 98 return resolver; 99 } 100 101 RadrootsPathResolver::current() 102 } 103 104 fn log_name_from_stem(stem: &str) -> Option<String> { 105 if stem.is_empty() { 106 None 107 } else { 108 Some(format!("{stem}.log")) 109 } 110 } 111 112 fn env_value(key: &str) -> Option<String> { 113 #[cfg(test)] 114 if test_hooks::ignore_env() { 115 return None; 116 } 117 let value = std::env::var(key).ok()?; 118 normalize_env_value(&value) 119 } 120 121 fn normalize_env_value(value: &str) -> Option<String> { 122 let trimmed = value.trim(); 123 if trimmed.is_empty() { 124 None 125 } else { 126 Some(trimmed.to_string()) 127 } 128 } 129 130 fn resolve_default_level(env_level: Option<String>, default_level: Option<&str>) -> Option<String> { 131 if let Some(level) = env_level { 132 Some(level) 133 } else { 134 default_level.map(str::to_string) 135 } 136 } 137 138 #[cfg(test)] 139 mod tests;