rust_loguru/
error.rs

1//! Error handling utilities for rust-loguru
2//!
3//! - Extension traits for Result/Option
4//! - Error chain capturing
5//! - Panic hook installation
6//! - Source location capture
7//! - Helper methods for error logging
8
9use std::error::Error as StdError;
10use std::fmt::{self, Debug, Display};
11use std::panic::{self, PanicHookInfo};
12use std::sync::Once;
13
14/// Extension trait for Result to add logging and context
15pub trait ResultExt<T, E> {
16    /// Log the error if it is Err, and return self
17    fn log_error(self, msg: &str) -> Self;
18    /// Attach context to the error
19    fn with_context<F, C>(self, f: F) -> Result<T, ContextError<E, C>>
20    where
21        F: FnOnce() -> C,
22        E: StdError + 'static,
23        C: Display + Debug + Send + Sync + 'static;
24}
25
26impl<T, E> ResultExt<T, E> for Result<T, E>
27where
28    E: StdError + 'static,
29{
30    fn log_error(self, msg: &str) -> Self {
31        if let Err(ref e) = self {
32            eprintln!("[ERROR] {}: {}", msg, e);
33        }
34        self
35    }
36
37    fn with_context<F, C>(self, f: F) -> Result<T, ContextError<E, C>>
38    where
39        F: FnOnce() -> C,
40        C: Display + Debug + Send + Sync + 'static,
41    {
42        self.map_err(|e| ContextError {
43            error: e,
44            context: f(),
45        })
46    }
47}
48
49/// Extension trait for Option to add logging
50pub trait OptionExt<T> {
51    /// Log if None, and return self
52    fn log_none(self, msg: &str) -> Self;
53}
54
55impl<T> OptionExt<T> for Option<T> {
56    fn log_none(self, msg: &str) -> Self {
57        if self.is_none() {
58            eprintln!("[ERROR] {}: None value", msg);
59        }
60        self
61    }
62}
63
64/// Error type with context
65#[derive(Debug)]
66pub struct ContextError<E, C> {
67    pub error: E,
68    pub context: C,
69}
70
71impl<E, C> Display for ContextError<E, C>
72where
73    E: Display,
74    C: Display,
75{
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "{} (context: {})", self.error, self.context)
78    }
79}
80
81impl<E, C> StdError for ContextError<E, C>
82where
83    E: StdError + 'static,
84    C: Display + Debug + Send + Sync + 'static,
85{
86    fn source(&self) -> Option<&(dyn StdError + 'static)> {
87        Some(&self.error)
88    }
89}
90
91/// Capture the error chain as a Vec of Strings
92pub fn error_chain(mut err: &(dyn StdError + 'static)) -> Vec<String> {
93    let mut chain = Vec::new();
94    loop {
95        chain.push(format!("{}", err));
96        match err.source() {
97            Some(source) => err = source,
98            None => break,
99        }
100    }
101    chain
102}
103
104static PANIC_HOOK_INIT: Once = Once::new();
105
106/// Install a panic hook that logs panics with location and payload
107pub fn install_panic_hook() {
108    PANIC_HOOK_INIT.call_once(|| {
109        let default_hook = panic::take_hook();
110        panic::set_hook(Box::new(move |info: &PanicHookInfo| {
111            let location = info
112                .location()
113                .map(|l| l.to_string())
114                .unwrap_or_else(|| "unknown location".to_string());
115            let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
116                s.to_string()
117            } else if let Some(s) = info.payload().downcast_ref::<String>() {
118                s.clone()
119            } else {
120                "Box<Any>".to_string()
121            };
122            eprintln!("[PANIC] at {}: {}", location, payload);
123            default_hook(info);
124        }));
125    });
126}
127
128/// Helper to extract source location (file, line, column)
129#[macro_export]
130macro_rules! source_location {
131    () => {
132        (file!(), line!(), column!())
133    };
134}
135
136/// Helper to log an error with source location
137#[macro_export]
138macro_rules! log_error_with_location {
139    ($err:expr) => {{
140        let (file, line, col) = $crate::source_location!();
141        eprintln!("[ERROR] at {}:{}:{}: {}", file, line, col, $err);
142    }};
143    ($err:expr, $msg:expr) => {{
144        let (file, line, col) = $crate::source_location!();
145        eprintln!("[ERROR] at {}:{}:{}: {}: {}", file, line, col, $msg, $err);
146    }};
147}