sherr/
lib.rs

1pub use backtrace;
2#[cfg(feature = "impl")]
3pub use chrono;
4#[cfg(feature = "impl")]
5pub use fern;
6#[cfg(feature = "impl")]
7pub use libc;
8#[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))]
9#[allow(unused_imports)]
10pub use log;
11
12pub use log::*;
13pub use anyhow::{self, Error, Result, Context, Chain, format_err, ensure, bail};
14
15#[derive(Debug)]
16pub struct Position {
17    pub file: String,
18    pub line: u32,
19    pub column: u32,
20}
21
22impl std::fmt::Display for Position {
23    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
24        write!(f, "{}:{}:{}", self.file, self.line, self.column)
25    }
26}
27
28#[macro_export]
29macro_rules! diag_position {
30    () => {{
31        $crate::Position {
32            file: std::path::PathBuf::from(file!()).file_name().unwrap().to_string_lossy().to_string(),
33            line: line!(),
34            column: column!(),
35        }
36    }};
37}
38
39#[macro_export]
40macro_rules! diag {
41    ($($arg:tt)+) => {{
42        $crate::error!(target: "diagnostics", $($arg)*);
43    }};
44}
45
46#[macro_export]
47macro_rules! diag_backtrace {
48    () => {{
49        $crate::error!(target: "diagnostics", "{:?}", $crate::backtrace::Backtrace::new());
50    }}
51}
52
53#[macro_export]
54macro_rules! diag_err {
55    () => {{
56        $crate::diag!("internal error at {}", $crate::diag_position!());
57        $crate::diag_backtrace!();
58        $crate::anyhow::anyhow!("internal error")
59    }};
60    ($($arg:tt)+) => {{
61        $crate::diag!("internal error at {}", $crate::diag_position!());
62        $crate::diag!($($arg)*);
63        $crate::diag_backtrace!();
64        $crate::anyhow::anyhow!($($arg)*)
65    }}
66}
67
68#[macro_export]
69macro_rules! bail_diag {
70    () => {{
71        $crate::diag!("internal error at {}", $crate::diag_position!());
72        $crate::diag_backtrace!();
73        $crate::anyhow::bail!("internal error");
74    }};
75    ($($arg:tt)+) => {{
76        $crate::diag!("internal error at {}", $crate::diag_position!());
77        $crate::diag!($($arg)*);
78        $crate::diag_backtrace!();
79        $crate::anyhow::bail!($($arg)*);
80    }}
81}
82
83#[macro_export]
84macro_rules! diag_unreachable {
85    () => {{
86        debug_assert!(false, "unreachable code reached");
87        $crate::diag!("unreachable code reached at {}", $crate::diag_position!());
88        $crate::diag_backtrace!();
89    }};
90    ($($arg:tt)+) => {{
91        debug_assert!(false, $($arg)*);
92        $crate::diag!($($arg)*);
93        $crate::diag_backtrace!();
94    }}
95}
96
97#[macro_export]
98macro_rules! diag_unreachable_err {
99    () => {{
100        $crate::diag_unreachable!();
101        $crate::anyhow::anyhow!("unreachable code reached at {}", $crate::diag_position!())
102    }};
103    ($($arg:tt)+) => {{
104        $crate::diag_unreachable!($($arg)*);
105        $crate::anyhow::anyhow!($($arg)*)
106    }}
107}
108
109#[macro_export]
110macro_rules! diag_unimplemented {
111    () => {{
112        debug_assert!(false, "unimplemented code reached");
113        $crate::diag!("unimplemented code reached at {}", $crate::diag_position!());
114        $crate::diag_backtrace!();
115    }};
116    ($($arg:tt)+) => {{
117        debug_assert!(false, $($arg)*);
118        $crate::diag!($($arg)*);
119        $crate::diag_backtrace!();
120    }}
121}
122
123#[macro_export]
124macro_rules! diag_unimplemented_err {
125    () => {{
126        $crate::diag_unreachable!();
127        $crate::anyhow::anyhow!("unimplemented code reached at {}", $crate::diag_position!())
128    }};
129    ($($arg:tt)+) => {{
130        $crate::diag_unreachable!($($arg)*);
131        $crate::anyhow::anyhow!($($arg)*)
132    }}
133}
134
135#[cfg(feature = "impl")]
136pub fn stdout_dispatch() -> fern::Dispatch {
137    use fern::colors::Color;
138    let colors = fern::colors::ColoredLevelConfig::new()
139        .trace(Color::White)
140        .debug(Color::Blue)
141        .info(Color::Green)
142        .warn(Color::Yellow)
143        .error(Color::Red);
144    fern::Dispatch::new()
145        .format(move |out, message, record| {
146            out.finish(format_args!(
147                "{} {}{}: {}",
148                chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
149                if atty::is(atty::Stream::Stdout) {
150                    format!("{}", colors.color(record.level()))
151                } else {
152                    format!("{}", record.level())
153                },
154                if record.level() == log::Level::Info || record.level() == log::Level::Warn {
155                    " "
156                } else {
157                    ""
158                },
159                message,
160            ))
161        })
162        .level(log::LevelFilter::Info)
163        .level_for("diagnostics", log::LevelFilter::Off)
164}
165
166#[cfg(feature = "impl")]
167pub fn stdout_dispatch_with_target() -> fern::Dispatch {
168    use fern::colors::Color;
169    let colors = fern::colors::ColoredLevelConfig::new()
170        .trace(Color::White)
171        .debug(Color::Blue)
172        .info(Color::Green)
173        .warn(Color::Yellow)
174        .error(Color::Red);
175    fern::Dispatch::new()
176        .format(move |out, message, record| {
177            out.finish(format_args!(
178                "{} {}{} {}: {}",
179                chrono::Local::now().format("[%Y-%m-%d %H:%M:%S]"),
180                colors.color(record.level()),
181                if record.level() == log::Level::Info || record.level() == log::Level::Warn {
182                    " "
183                } else {
184                    ""
185                },
186                record.target(),
187                message,
188            ))
189        })
190        .level(log::LevelFilter::Info)
191        .level_for("diagnostics", log::LevelFilter::Off)
192}
193
194#[cfg(feature = "impl")]
195pub fn diag_dispatch() -> fern::Dispatch {
196    fern::Dispatch::new()
197        .format(move |out, message, record| {
198            out.finish(format_args!(
199                "{} {}{} {}: {}",
200                chrono::Local::now().format("[%Y-%m-%d %H:%M:%S%.9f]"),
201                record.level(),
202                if record.level() == log::Level::Info {
203                    " "
204                } else {
205                    ""
206                },
207                record.target(),
208                message,
209            ))
210        })
211        .level(log::LevelFilter::Info)
212        .level_for("diagnostics", log::LevelFilter::Trace)
213}
214
215#[cfg(feature = "impl")]
216pub fn init_logger(
217    log_file: Option<impl AsRef<std::path::Path>>,
218) -> std::io::Result<fern::Dispatch> {
219    let mut dispatch = fern::Dispatch::new().chain(stdout_dispatch().chain(std::io::stdout()));
220    let log_file = if let Some(log_file) = log_file {
221        Some(log_file.as_ref().to_owned())
222    } else if let Ok(exe_path) = std::env::current_exe() {
223        exe_path.parent().map(|exe_dir| exe_dir.join(".diag.log"))
224    } else {
225        None
226    };
227    if let Some(log_file) = log_file {
228        let log_file = std::fs::OpenOptions::new()
229            .write(true)
230            .create(true)
231            .truncate(true)
232            .open(log_file)?;
233        dispatch = dispatch.chain(diag_dispatch().chain(log_file))
234    }
235    Ok(dispatch)
236}
237
238#[cfg(test)]
239mod tests;