commonlibsse_ng\skse/
logger.rs1use crate::rel::module::ModuleState;
2use crate::rex::win32::document_dir;
3use snafu::ResultExt as _;
4use std::path::{Path, PathBuf};
5#[cfg(feature = "tracing")]
6use std::sync::OnceLock;
7#[cfg(feature = "tracing")]
8use tracing_subscriber::{
9 Registry,
10 filter::LevelFilter,
11 fmt,
12 prelude::*,
13 reload::{self, Handle},
14};
15
16pub fn log_directory() -> Result<PathBuf, LogInitError> {
24 let mut path = document_dir().map_err(|_| LogInitError::NotFoundDocumentDir)?;
25 path.push("My Games");
26
27 let runtime =
28 ModuleState::map_or_init(|module| module.runtime).context(UnexpectedModuleStateSnafu)?;
29
30 if runtime.is_vr() {
31 path.push("Skyrim VR");
32 } else if Path::new("steam_api64.dll").exists() {
33 if Path::new("openvr_api.dll").exists() {
34 path.push("Skyrim VR");
35
36 } else {
39 path.push("Skyrim Special Edition");
40 }
41 } else {
42 path.push("Skyrim Special Edition GOG");
43 }
44 path.push("SKSE");
45 Ok(path)
46}
47
48#[cfg(feature = "tracing")]
50static RELOAD_HANDLE: OnceLock<Handle<LevelFilter, Registry>> = OnceLock::new();
51#[cfg(feature = "tracing")]
52static GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new();
53
54#[cfg(feature = "tracing")]
60pub fn init<P, D>(log_dir: P, file_name: D, level: LevelFilter) -> Result<(), LogInitError>
61where
62 P: AsRef<Path>,
63 D: AsRef<Path>,
64{
65 _init(log_dir.as_ref(), file_name.as_ref(), level)
66}
67
68#[cfg(feature = "tracing")]
74pub fn init_with_log_dir<P>(file_name: P, level: LevelFilter) -> Result<(), LogInitError>
75where
76 P: AsRef<Path>,
77{
78 _init(&log_directory()?, file_name.as_ref(), level)
79}
80
81#[cfg(feature = "tracing")]
82fn _init(log_dir: &Path, file_name: &Path, level: LevelFilter) -> Result<(), LogInitError> {
83 use tracing_appender::non_blocking::NonBlockingBuilder;
84
85 let _ = std::fs::create_dir_all(log_dir);
86 let file = std::fs::File::create(log_dir.join(file_name))
87 .map_err(|_e| LogInitError::FailedCreateLogFile)?;
88 let (non_blocking, guard) = NonBlockingBuilder::default().finish(file);
89
90 let fmt_layer = fmt::layer()
93 .compact()
94 .with_ansi(false)
95 .with_file(true)
96 .with_line_number(true)
97 .with_target(false)
98 .with_writer(non_blocking);
99
100 let (filter, reload_handle) = reload::Layer::new(level);
101 tracing_subscriber::registry().with(filter).with(fmt_layer).init();
102
103 GUARD.set(guard).map_err(|_e| LogInitError::FailedInitLog)?;
104 RELOAD_HANDLE.set(reload_handle).map_err(|_e| LogInitError::FailedInitLog)
105}
106
107#[cfg(feature = "tracing")]
112pub fn change_level(log_level: &str) -> Result<(), LogReloadError> {
113 use snafu::ResultExt as _;
114
115 let new_filter =
116 <LevelFilter as core::str::FromStr>::from_str(log_level).unwrap_or_else(|_e| {
117 tracing::warn!("Unknown log level: {log_level}. Fallback to `error`");
118 LevelFilter::ERROR
119 });
120
121 RELOAD_HANDLE.get().map_or(Err(LogReloadError::UninitLog), |log| {
122 log.modify(|filter| *filter = new_filter).context(ReloadSnafu)
123 })
124}
125
126#[cfg(feature = "tracing")]
128#[derive(Debug, snafu::Snafu)]
129pub enum LogReloadError {
130 UninitLog,
132
133 Reload { source: tracing_subscriber::reload::Error },
135}
136
137#[derive(Debug, snafu::Snafu)]
139pub enum LogInitError {
140 NotFoundDocumentDir,
142
143 UnexpectedModuleState { source: crate::rel::module::ModuleStateError },
145
146 FailedCreateLogFile,
148
149 FailedInitLog,
151}
152
153#[cfg(feature = "test_on_ci")]
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_log_directory() {
160 let log_dir = log_directory().unwrap();
161 println!("{}", log_dir.display());
162 }
163}