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//! [](https://crates.io/crates/tracing-unwrap)
6//! [](https://docs.rs/tracing-unwrap)
7//! [](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}