log_io/error.rs
1//! Crate-level error type.
2
3use std::fmt;
4use std::io;
5
6/// Crate result alias.
7pub type Result<T> = core::result::Result<T, Error>;
8
9/// Errors produced by the logging pipeline.
10///
11/// Logging is best-effort by design: [`crate::Logger::log`] and the
12/// macros never return these errors. They reach you through
13/// [`crate::Logger::try_log`] / [`crate::Logger::try_emit`], through
14/// [`crate::Logger::flush`] and [`crate::Logger::shutdown`], and
15/// through the handler installed with
16/// [`crate::LoggerBuilder::on_error`].
17///
18/// The enum is `#[non_exhaustive]`; match it with a wildcard arm.
19///
20/// # Examples
21///
22/// ```
23/// use log_io::Error;
24///
25/// let err = Error::from(std::io::Error::other("disk full"));
26/// assert_eq!(err.to_string(), "log sink I/O error: disk full");
27/// assert!(std::error::Error::source(&err).is_some());
28/// ```
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum Error {
32 /// The sink's destination returned an I/O error (disk full, broken
33 /// pipe, permission denied, ...). Retrying may help for transient
34 /// conditions; persistent failures usually need operator action.
35 /// The record that triggered it was not written to that sink, but
36 /// every other sink still received it.
37 Io(io::Error),
38 /// A formatter failed. With the built-in formats this only happens
39 /// when a lazily formatted value (`Value::Display`, `Value::Debug`,
40 /// `Value::Error`) or a message's format arguments return an error
41 /// from their own `fmt` implementation; it indicates a bug in that
42 /// implementation or in a custom [`crate::format::Format`].
43 Format(fmt::Error),
44 /// A sink or format rejected its configuration. Among the built-in
45 /// components only [`crate::sink::NonBlockingBuilder::build`]
46 /// returns it (for an invalid thread name); fix the configuration.
47 Configuration(&'static str),
48 /// The sink cannot serve the request. Either its background worker
49 /// exited (for example after the writer panicked), in which case
50 /// records sent to it are lost and the sink must be rebuilt; or a
51 /// flush or shutdown was requested from the sink's own worker
52 /// thread (its writer or `on_error` handler), which cannot wait for
53 /// itself.
54 Closed,
55 /// An error from a custom sink that does not fit the other
56 /// variants. Construct with [`Error::custom`].
57 Custom(Box<dyn std::error::Error + Send + Sync + 'static>),
58}
59
60impl Error {
61 /// Wrap an arbitrary error from a custom sink.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// let err = log_io::Error::custom(std::fmt::Error);
67 /// assert!(matches!(err, log_io::Error::Custom(_)));
68 /// ```
69 pub fn custom<E>(error: E) -> Self
70 where
71 E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
72 {
73 Self::Custom(error.into())
74 }
75}
76
77impl fmt::Display for Error {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Self::Io(e) => write!(f, "log sink I/O error: {e}"),
81 Self::Format(_) => f.write_str("log record formatting failed"),
82 Self::Configuration(msg) => write!(f, "log sink configuration error: {msg}"),
83 Self::Closed => f.write_str("log sink is closed and no longer accepts records"),
84 Self::Custom(e) => write!(f, "log sink error: {e}"),
85 }
86 }
87}
88
89impl std::error::Error for Error {
90 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91 match self {
92 Self::Io(e) => Some(e),
93 Self::Format(e) => Some(e),
94 Self::Custom(e) => Some(&**e),
95 Self::Configuration(_) | Self::Closed => None,
96 }
97 }
98}
99
100impl From<io::Error> for Error {
101 fn from(value: io::Error) -> Self {
102 Self::Io(value)
103 }
104}
105
106impl From<fmt::Error> for Error {
107 fn from(value: fmt::Error) -> Self {
108 Self::Format(value)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::error::Error as _;
116
117 #[test]
118 fn test_error_display_has_context_for_every_variant() {
119 let cases: [(Error, &str); 5] = [
120 (
121 io::Error::other("disk full").into(),
122 "log sink I/O error: disk full",
123 ),
124 (fmt::Error.into(), "log record formatting failed"),
125 (
126 Error::Configuration("bad"),
127 "log sink configuration error: bad",
128 ),
129 (
130 Error::Closed,
131 "log sink is closed and no longer accepts records",
132 ),
133 (Error::custom("boom"), "log sink error: boom"),
134 ];
135 for (err, expected) in cases {
136 assert_eq!(err.to_string(), expected);
137 }
138 }
139
140 #[test]
141 fn test_error_source_chain() {
142 assert!(Error::from(io::Error::other("x")).source().is_some());
143 assert!(Error::from(fmt::Error).source().is_some());
144 assert!(Error::custom("x").source().is_some());
145 assert!(Error::Configuration("x").source().is_none());
146 assert!(Error::Closed.source().is_none());
147 }
148}