Skip to main content

systemctl_tui/
utils.rs

1use std::path::PathBuf;
2
3use anyhow::{anyhow, Context, Result};
4use better_panic::Settings;
5use directories::ProjectDirs;
6use tracing::{error, level_filters::LevelFilter};
7use tracing_appender::{
8  non_blocking::WorkerGuard,
9  rolling::{RollingFileAppender, Rotation},
10};
11use tracing_subscriber::{
12  self, filter::EnvFilter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
13};
14
15pub fn initialize_panic_handler() {
16  std::panic::set_hook(Box::new(|panic_info| {
17    if let Err(r) = crate::terminal::exit() {
18      error!("Unable to exit Terminal: {r:?}");
19    }
20
21    Settings::auto().most_recent_first(false).lineno_suffix(true).create_panic_handler()(panic_info);
22    std::process::exit(libc::EXIT_FAILURE);
23  }));
24}
25
26pub fn get_data_dir() -> Result<PathBuf> {
27  let directory = if let Ok(s) = std::env::var("SYSTEMCTL_TUI_DATA") {
28    PathBuf::from(s)
29  } else if let Some(proj_dirs) = ProjectDirs::from("com", "rgwood", "systemctl-tui") {
30    proj_dirs.data_local_dir().to_path_buf()
31  } else {
32    return Err(anyhow!("Unable to find data directory for systemctl-tui"));
33  };
34  Ok(directory)
35}
36
37pub fn get_config_dir() -> Result<PathBuf> {
38  let directory = if let Ok(s) = std::env::var("SYSTEMCTL_TUI_CONFIG") {
39    PathBuf::from(s)
40  } else if let Some(proj_dirs) = ProjectDirs::from("com", "rgwood", "systemctl-tui") {
41    proj_dirs.config_local_dir().to_path_buf()
42  } else {
43    return Err(anyhow!("Unable to find config directory for systemctl-tui"));
44  };
45  Ok(directory)
46}
47
48pub fn initialize_logging(enable_file_logging: bool) -> Result<Option<WorkerGuard>> {
49  let mut guard = None;
50
51  let file_layer = if enable_file_logging {
52    let directory = get_data_dir()?;
53    std::fs::create_dir_all(directory.clone()).context(format!("{directory:?} could not be created"))?;
54
55    // create a file appender that rolls daily
56    let file_appender = RollingFileAppender::new(Rotation::DAILY, &directory, "systemctl-tui.log");
57
58    // create a non-blocking writer
59    let (non_blocking, g) = tracing_appender::non_blocking(file_appender);
60
61    // We must return this guard to main.rs and keep it alive
62    guard = Some(g);
63
64    // Log initialization info only if we are actually logging
65    tracing::info!(directory = %directory.display(), "Logging initialized");
66
67    // create a layer for the file logger
68    Some(
69      tracing_subscriber::fmt::layer()
70        .with_writer(non_blocking)
71        .with_file(true)
72        .with_line_number(true)
73        .with_target(false)
74        .with_ansi(false)
75        .with_filter(EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy()),
76    )
77  } else {
78    None
79  };
80
81  tui_logger::init_logger(tui_logger::LevelFilter::Debug)?;
82
83  let tui_layer = tui_logger::TuiTracingSubscriberLayer
84    .with_filter(EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy());
85
86  tracing_subscriber::registry().with(file_layer).with(tui_layer).init();
87
88  Ok(guard)
89}
90
91/// Similar to the `std::dbg!` macro, but generates `tracing` events rather
92/// than printing to stdout.
93///
94/// By default, the verbosity level for the generated events is `DEBUG`, but
95/// this can be customized.
96#[macro_export]
97macro_rules! trace_dbg {
98    (target: $target:expr, level: $level:expr, $ex:expr) => {{
99        match $ex {
100            value => {
101                tracing::event!(target: $target, $level, ?value, stringify!($ex));
102                value
103            }
104        }
105    }};
106    (level: $level:expr, $ex:expr) => {
107        trace_dbg!(target: module_path!(), level: $level, $ex)
108    };
109    (target: $target:expr, $ex:expr) => {
110        trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
111    };
112    ($ex:expr) => {
113        trace_dbg!(level: tracing::Level::DEBUG, $ex)
114    };
115}
116
117pub fn version() -> String {
118  let author = clap::crate_authors!();
119
120  let version = env!("CARGO_PKG_VERSION");
121
122  let config_dir_path = get_config_dir().unwrap().display().to_string();
123  let data_dir_path = get_data_dir().unwrap().display().to_string();
124
125  format!(
126    "\
127{version}
128
129Authors: {author}
130
131Config directory: {config_dir_path}
132Data directory: {data_dir_path}"
133  )
134}