1use std::{io::Write, path::PathBuf, sync::atomic::AtomicBool};
2
3use anyhow::{anyhow, Context, Result};
4use better_panic::Settings;
5use directories::ProjectDirs;
6use lazy_static::lazy_static;
7use tracing::{error, level_filters::LevelFilter};
8use tracing_appender::{
9 non_blocking::WorkerGuard,
10 rolling::{RollingFileAppender, Rotation},
11};
12use tracing_subscriber::{
13 self, filter::EnvFilter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
14};
15
16lazy_static! {
17 static ref TRACE_FILE_NAME: PathBuf = {
18 let directory = get_data_dir().expect("Unable to get data directory");
19 let timestamp_iso8601 = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S");
20 directory.join(format!("systemctl-tui-trace-{}.log", timestamp_iso8601))
21 };
22}
23
24static TRACING_ENABLED: AtomicBool = AtomicBool::new(false);
25
26pub fn initialize_panic_handler() {
27 std::panic::set_hook(Box::new(|panic_info| {
28 if let Err(r) = crate::terminal::exit() {
29 error!("Unable to exit Terminal: {r:?}");
30 }
31
32 Settings::auto().most_recent_first(false).lineno_suffix(true).create_panic_handler()(panic_info);
33 std::process::exit(libc::EXIT_FAILURE);
34 }));
35}
36
37pub fn get_data_dir() -> Result<PathBuf> {
38 let directory = if let Ok(s) = std::env::var("SYSTEMCTL_TUI_DATA") {
39 PathBuf::from(s)
40 } else if let Some(proj_dirs) = ProjectDirs::from("com", "rgwood", "systemctl-tui") {
41 proj_dirs.data_local_dir().to_path_buf()
42 } else {
43 return Err(anyhow!("Unable to find data directory for systemctl-tui"));
44 };
45 Ok(directory)
46}
47
48pub fn get_config_dir() -> Result<PathBuf> {
49 let directory = if let Ok(s) = std::env::var("SYSTEMCTL_TUI_CONFIG") {
50 PathBuf::from(s)
51 } else if let Some(proj_dirs) = ProjectDirs::from("com", "rgwood", "systemctl-tui") {
52 proj_dirs.config_local_dir().to_path_buf()
53 } else {
54 return Err(anyhow!("Unable to find config directory for systemctl-tui"));
55 };
56 Ok(directory)
57}
58
59pub fn initialize_logging(enable_tracing: bool) -> Result<WorkerGuard> {
60 let directory = get_data_dir()?;
61 std::fs::create_dir_all(directory.clone()).context(format!("{directory:?} could not be created"))?;
62 let file_appender = RollingFileAppender::new(Rotation::DAILY, &directory, "systemctl-tui.log");
66
67 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
69
70 let file_layer = tracing_subscriber::fmt::layer()
72 .with_writer(non_blocking)
73 .with_file(true)
74 .with_line_number(true)
75 .with_target(false)
76 .with_ansi(false)
77 .with_filter(EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy());
78
79 let tui_layer = tui_logger::tracing_subscriber_layer()
80 .with_filter(EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy());
81
82 tracing_subscriber::registry().with(file_layer).with(tui_layer).init();
83
84 if enable_tracing {
85 TRACING_ENABLED.store(true, std::sync::atomic::Ordering::Relaxed);
86 let mut trace_file = std::fs::File::create(&*TRACE_FILE_NAME).unwrap();
87 trace_file.write_all(b"[\n").unwrap(); }
89
90 let directory = directory.to_string_lossy().into_owned();
91 tracing::info!(directory, "Logging initialized");
92
93 Ok(guard)
94}
95
96pub fn log_perf_event(event: &str, duration: std::time::Duration) {
100 if !TRACING_ENABLED.load(std::sync::atomic::Ordering::Relaxed) {
101 return;
102 }
103 let log_path = &*TRACE_FILE_NAME;
104 let system_time = std::time::SystemTime::now();
105
106 let event = format!(
107 r#"{{
108 "name": "{}",
109 "cat": "PERF",
110 "ph": "X",
111 "ts": {},
112 "dur": {}
113}}"#,
114 event,
115 system_time.duration_since(std::time::UNIX_EPOCH).unwrap().as_micros(),
116 duration.as_micros()
117 );
118
119 let mut file = std::fs::OpenOptions::new().append(true).create(true).open(log_path).unwrap();
120 file.write_all(event.as_bytes()).unwrap();
121 file.write_all(b",\n").unwrap();
122}
123
124#[macro_export]
130macro_rules! trace_dbg {
131 (target: $target:expr, level: $level:expr, $ex:expr) => {{
132 match $ex {
133 value => {
134 tracing::event!(target: $target, $level, ?value, stringify!($ex));
135 value
136 }
137 }
138 }};
139 (level: $level:expr, $ex:expr) => {
140 trace_dbg!(target: module_path!(), level: $level, $ex)
141 };
142 (target: $target:expr, $ex:expr) => {
143 trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
144 };
145 ($ex:expr) => {
146 trace_dbg!(level: tracing::Level::DEBUG, $ex)
147 };
148}
149
150pub fn version() -> String {
151 let author = clap::crate_authors!();
152
153 let version = env!("CARGO_PKG_VERSION");
154
155 let config_dir_path = get_config_dir().unwrap().display().to_string();
156 let data_dir_path = get_data_dir().unwrap().display().to_string();
157
158 format!(
159 "\
160{version}
161
162Authors: {author}
163
164Config directory: {config_dir_path}
165Data directory: {data_dir_path}"
166 )
167}