tracing_unwrap/
lib.rs

1//! This crate provides `.unwrap_or_log()` and `.expect_or_log()` methods on `Result` and `Option` types that log failed unwraps to a [`tracing::Subscriber`]. This is useful when, for example, you are logging to syslog or a database, and you want your unwrap failures to show up there instead of being printed to `stderr`.
2//!
3//! Its API aims to mirror Rust's `std` — see all the [supported methods](#methods) below. Failed unwraps are logged at a level of [`ERROR`].
4//!
5//! [![crates.io](https://img.shields.io/crates/v/tracing-unwrap?label=latest)](https://crates.io/crates/tracing-unwrap)
6//! [![Documentation](https://docs.rs/tracing-unwrap/badge.svg)](https://docs.rs/tracing-unwrap)
7//! [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/abreis/tracing-unwrap)
8//!
9//! ### Usage
10//! Add the following to your `Cargo.toml`:
11//! ```toml
12//! tracing-unwrap = "1.0"
13//! ```
14//!
15//! Next, bring the [`ResultExt`] and/or [`OptionExt`] traits into scope, and make use of the new logging methods.
16//! ```ignore
17//! use tracing_unwrap::ResultExt;
18//!
19//! tracing_subscriber::fmt().init();
20//! let not_great: Result<(), _> = Result::Err("not terrible");
21//!
22//! // Logs the failed unwrap and panics
23//! not_great.unwrap_or_log();
24//! ```
25//!
26//! ### Methods
27//! | `std` method                               | `tracing-unwrap` form                 | trait         |
28//! | ------------------------------------------ | ------------------------------------- | ------------- |
29//! | [`Result::ok()`]                           | [`Result::ok_or_log()`]               | [`ResultExt`] |
30//! | [`Result::unwrap()`]                       | [`Result::unwrap_or_log()`]           | [`ResultExt`] |
31//! | [`Result::expect(msg)`]                    | [`Result::expect_or_log(msg)`]        | [`ResultExt`] |
32//! | [`Result::unwrap_err()`]                   | [`Result::unwrap_err_or_log()`]       | [`ResultExt`] |
33//! | [`Result::expect_err(msg)`]                | [`Result::expect_err_or_log(msg)`]    | [`ResultExt`] |
34//! | [`Option::unwrap()`]                       | [`Option::unwrap_or_log()`]           | [`OptionExt`] |
35//! | [`Option::expect(msg)`]                    | [`Option::expect_or_log(msg)`]        | [`OptionExt`] |
36//! | [`Option::unwrap_none()`]<sup>†</sup>      | [`Option::unwrap_none_or_log()`]      | [`OptionExt`] |
37//! | [`Option::expect_none(msg)`]<sup>†</sup>   | [`Option::expect_none_or_log(msg)`]   | [`OptionExt`] |
38//!
39//! *†: no longer in `std`, see [`rust-lang/rust#62633`](https://github.com/rust-lang/rust/issues/62633)*<br/>
40//!
41//!
42//! ### Features
43//! * **`panic-quiet`**: causes failed unwraps to panic with an empty message.<br/>
44//!   This feature is enabled by default — if you'd like the unwrap error message to also show in the panic message, disable default features in your `Cargo.toml` as follows:<br/>
45//!   `tracing-unwrap = { version = "1.0", default-features = false }`
46//!
47//! * **`log-location`**: calls [`std::panic::Location::caller()`] to determine the location of a failed unwrap.
48//!
49//! [`tracing::Subscriber`]: https://docs.rs/tracing/*/tracing/trait.Subscriber.html
50//! [`ResultExt`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html
51//! [`OptionExt`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.OptionExt.html
52//! [`ERROR`]: https://docs.rs/tracing/*/tracing/struct.Level.html#associatedconstant.ERROR
53//! [`Result::ok()`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.ok
54//! [`Result::unwrap()`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap
55//! [`Result::expect(msg)`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.expect
56//! [`Result::unwrap_err()`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err
57//! [`Result::expect_err(msg)`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.expect_err
58//! [`Option::unwrap()`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap
59//! [`Option::expect(msg)`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.expect
60//! [`Option::unwrap_none()`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_none
61//! [`Option::expect_none(msg)`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.expect_none
62//! [`Result::ok_or_log()`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html#tymethod.ok_or_log
63//! [`Result::unwrap_or_log()`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html#tymethod.unwrap_or_log
64//! [`Result::expect_or_log(msg)`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html#tymethod.expect_or_log
65//! [`Result::unwrap_err_or_log()`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html#tymethod.unwrap_err_or_log
66//! [`Result::expect_err_or_log(msg)`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.ResultExt.html#tymethod.expect_err_or_log
67//! [`Option::unwrap_or_log()`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.OptionExt.html#tymethod.unwrap_or_log
68//! [`Option::expect_or_log(msg)`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.OptionExt.html#tymethod.expect_or_log
69//! [`Option::unwrap_none_or_log()`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.OptionExt.html#tymethod.unwrap_none_or_log
70//! [`Option::expect_none_or_log(msg)`]: https://docs.rs/tracing-unwrap/*/tracing_unwrap/trait.OptionExt.html#tymethod.expect_none_or_log
71//! [`std::panic::Location::caller()`]: https://doc.rust-lang.org/std/panic/struct.Location.html#method.caller
72
73use std::fmt;
74
75//
76// Extension trait for Result types.
77//
78
79/// Extension trait for Result types.
80pub trait ResultExt<T, E> {
81    /// Converts `self` into an [`Option<T>`], consuming `self`, and logs the
82    /// error, if any, to a [`tracing::Subscriber`] at a [`WARN`] level.
83    ///
84    /// [`WARN`]: /tracing/0.1/tracing/struct.Level.html#associatedconstant.WARN
85    fn ok_or_log(self) -> Option<T>
86    where
87        E: fmt::Debug;
88
89    /// Unwraps a result, yielding the content of an [`Ok`].
90    ///
91    /// # Panics
92    ///
93    /// Panics if the value is an [`Err`], logging a message provided by the
94    /// [`Err`]'s value to a [`tracing::Subscriber`] at an [`ERROR`] level.
95    ///
96    /// [`ERROR`]: /tracing/0.1/tracing/struct.Level.html#associatedconstant.ERROR
97    fn unwrap_or_log(self) -> T
98    where
99        E: fmt::Debug;
100
101    /// Unwraps a result, yielding the content of an [`Ok`].
102    ///
103    /// # Panics
104    ///
105    /// Panics if the value is an [`Err`], logging the passed message and the
106    /// content of the [`Err`] to a [`tracing::Subscriber`] at an [`ERROR`] level.
107    ///
108    /// [`ERROR`]: /tracing/0.1/tracing/struct.Level.html#associatedconstant.ERROR
109    fn expect_or_log(self, msg: &str) -> T
110    where
111        E: fmt::Debug;
112
113    /// Unwraps a result, yielding the content of an [`Err`].
114    ///
115    /// # Panics
116    ///
117    /// Panics if the value is an [`Ok`], logging a message provided by the
118    /// [`Ok`]'s value to a [`tracing::Subscriber`] at an [`ERROR`] level.
119    ///
120    /// [`ERROR`]: /tracing/0.1/tracing/struct.Level.html#associatedconstant.ERROR
121    fn unwrap_err_or_log(self) -> E
122    where
123        T: fmt::Debug;
124
125    /// Unwraps a result, yielding the content of an [`Err`].
126    ///
127    /// # Panics
128    ///
129    /// Panics if the value is an [`Ok`], logging the passed message and the
130    /// content of the [`Ok`] to a [`tracing::Subscriber`] at an [`ERROR`] level.
131    ///
132    /// [`ERROR`]: /tracing/0.1/tracing/struct.Level.html#associatedconstant.ERROR
133    fn expect_err_or_log(self, msg: &str) -> E
134    where
135        T: fmt::Debug;
136}
137
138impl<T, E> ResultExt<T, E> for Result<T, E> {
139    #[inline]
140    #[track_caller]
141    fn ok_or_log(self) -> Option<T>
142    where
143        E: fmt::Debug,
144    {
145        match self {
146            Ok(t) => Some(t),
147            Err(e) => {
148                discarded_with("called `Result::ok_or_log` on an `Err` value", &e);
149                None
150            }
151        }
152    }
153
154    #[inline]
155    #[track_caller]
156    fn unwrap_or_log(self) -> T
157    where
158        E: fmt::Debug,
159    {
160        match self {
161            Ok(t) => t,
162            Err(e) => failed_with("called `Result::unwrap_or_log()` on an `Err` value", &e),
163        }
164    }
165
166    #[inline]
167    #[track_caller]
168    fn expect_or_log(self, msg: &str) -> T
169    where
170        E: fmt::Debug,
171    {
172        match self {
173            Ok(t) => t,
174            Err(e) => failed_with(msg, &e),
175        }
176    }
177
178    #[inline]
179    #[track_caller]
180    fn unwrap_err_or_log(self) -> E
181    where
182        T: fmt::Debug,
183    {
184        match self {
185            Ok(t) => failed_with("called `Result::unwrap_err_or_log()` on an `Ok` value", &t),
186            Err(e) => e,
187        }
188    }
189
190    #[inline]
191    #[track_caller]
192    fn expect_err_or_log(self, msg: &str) -> E
193    where
194        T: fmt::Debug,
195    {
196        match self {
197            Ok(t) => failed_with(msg, &t),
198            Err(e) => e,
199        }
200    }
201}
202
203//
204// Extension trait for Option types.
205//
206
207/// Extension trait for Option types.
208pub trait OptionExt<T> {
209    /// Moves the value `v` out of the `Option<T>` if it is [`Some(v)`].
210    ///
211    /// In general, because this function may panic, its use is discouraged.
212    /// Instead, prefer to use pattern matching and handle the [`None`]
213    /// case explicitly.
214    ///
215    /// # Panics
216    ///
217    /// Panics if the self value equals [`None`], logging an error message to a
218    /// [`tracing::Subscriber`] at an [`ERROR`] level.
219    fn unwrap_or_log(self) -> T;
220
221    /// Unwraps an option, yielding the content of a [`Some`].
222    ///
223    /// # Panics
224    ///
225    /// Panics if the value is a [`None`], logging the passed message to a
226    /// [`tracing::Subscriber`] at an [`ERROR`] level.
227    fn expect_or_log(self, msg: &str) -> T;
228
229    /// Unwraps an option, expecting [`None`] and returning nothing.
230    ///
231    /// # Panics
232    ///
233    /// Panics if the value is a [`Some`], logging a message derived from the
234    /// [`Some`]'s value to a [`tracing::Subscriber`] at an [`ERROR`] level.
235    fn unwrap_none_or_log(self)
236    where
237        T: fmt::Debug;
238
239    /// Unwraps an option, expecting [`None`] and returning nothing.
240    ///
241    /// # Panics
242    ///
243    /// Panics if the value is a [`Some`], logging the passed message and the
244    /// content of the [`Some`] to a [`tracing::Subscriber`] at an [`ERROR`] level.
245    fn expect_none_or_log(self, msg: &str)
246    where
247        T: fmt::Debug;
248}
249
250impl<T> OptionExt<T> for Option<T> {
251    #[inline]
252    #[track_caller]
253    fn unwrap_or_log(self) -> T {
254        match self {
255            Some(val) => val,
256            None => failed("called `Option::unwrap_or_log()` on a `None` value"),
257        }
258    }
259
260    #[inline]
261    #[track_caller]
262    fn expect_or_log(self, msg: &str) -> T {
263        match self {
264            Some(val) => val,
265            None => failed(msg),
266        }
267    }
268
269    #[inline]
270    #[track_caller]
271    fn unwrap_none_or_log(self)
272    where
273        T: fmt::Debug,
274    {
275        if let Some(val) = self {
276            failed_with(
277                "called `Option::unwrap_none_or_log()` on a `Some` value",
278                &val,
279            );
280        }
281    }
282
283    #[inline]
284    #[track_caller]
285    fn expect_none_or_log(self, msg: &str)
286    where
287        T: fmt::Debug,
288    {
289        if let Some(val) = self {
290            failed_with(msg, &val);
291        }
292    }
293}
294
295//
296// Helper functions.
297//
298
299#[inline(never)]
300#[cold]
301#[track_caller]
302fn failed(msg: &str) -> ! {
303    #[cfg(feature = "log-location")]
304    {
305        let location = std::panic::Location::caller();
306        tracing::error!(
307            unwrap.filepath = location.file(),
308            unwrap.lineno = location.line(),
309            unwrap.columnno = location.column(),
310            "{}",
311            msg
312        );
313    }
314
315    #[cfg(not(feature = "log-location"))]
316    tracing::error!("{}", msg);
317
318    #[cfg(feature = "panic-quiet")]
319    panic!();
320    #[cfg(not(feature = "panic-quiet"))]
321    panic!("{}", msg)
322}
323
324#[inline(never)]
325#[cold]
326#[track_caller]
327fn failed_with(msg: &str, value: &dyn fmt::Debug) -> ! {
328    #[cfg(feature = "log-location")]
329    {
330        let location = std::panic::Location::caller();
331        tracing::error!(
332            unwrap.filepath = location.file(),
333            unwrap.lineno = location.line(),
334            unwrap.columnno = location.column(),
335            "{}: {:?}",
336            msg,
337            &value
338        );
339    }
340
341    #[cfg(not(feature = "log-location"))]
342    tracing::error!("{}: {:?}", msg, &value);
343
344    #[cfg(feature = "panic-quiet")]
345    panic!();
346    #[cfg(not(feature = "panic-quiet"))]
347    panic!("{}: {:?}", msg, &value);
348}
349
350#[inline(never)]
351#[cold]
352#[track_caller]
353fn discarded_with(msg: &str, value: &dyn fmt::Debug) {
354    #[cfg(feature = "log-location")]
355    {
356        let location = std::panic::Location::caller();
357        tracing::warn!(
358            unwrap.filepath = location.file(),
359            unwrap.lineno = location.line(),
360            unwrap.columnno = location.column(),
361            "{}: {:?}",
362            msg,
363            &value
364        );
365    }
366
367    #[cfg(not(feature = "log-location"))]
368    tracing::warn!("{}: {:?}", msg, &value);
369}