Scenario:
The codebase generally uses Result<X, E>
for return types.
As we know, ?
effectively converts into a return Err(e)
when used.
But, I have some telemetry information that needs to be emitted on error. Essentially, I am constructing a stack trace for debugging.
Slightly pseudocode example:
fn f()->Result<X, E> { ... }
fn g(ctx: &mut Context) -> Result<Y, E> {
match f() {
Ok(x) => Ok(x.to_y()),
Err(e) => {
ctx.log(e);
return e
}
}
I would like to be able to essentially run a standard hook on error-exit, so I could use the ?
operator, but also gather the error rationale in the telemetry system.
I believe this corresponds to an after advice or after-throwing advice in aspect oriented programming approaches.
What’s the usual practice here to shrink the boilerplate?
2
If you want this only in some cases:
Add inspect_err()
or map_err()
(if you need to change the error):
fn f() -> Result<X, E> {
// ...
}
fn g(ctx: &mut Context) -> Result<Y, E> {
Ok(f().inspect_err(|e| ctx.log(e))?.to_y())
}
If you want this on all (or many) errors:
Create a custom error type. There is a hook you can use to customize the behavior of the ?
operator: if invokes From::from()
to convert the error. You can log there:
pub struct LogError<E>(pub E);
impl From<E> for LogError<E> {
fn from(err: E) -> Self {
ctx.log(&err);
Self(err)
}
}
fn f() -> Result<X, E> {
// ...
}
fn g(ctx: &mut Context) -> Result<Y, LogError<E>> {
Ok(f()?.to_y())
}
You can’t use this method to log an error when it is already a LogError
, but you can use a backtrace or context to do that.