Error observers
Error observers are a mechanism to intercept errors.
They are primarily designed for error reporting—e.g. you can use them to log errors,
increment a metric counter, etc.
use pavex::error_observer;
use tracing_log_error::log_error;
#[error_observer]
pub async fn emit_error_log(e: &pavex::Error) {
log_error!(e, "An error occurred during request processing");
}
Signature
Pavex accepts a wide range of function signatures for error observers, as long as they satisfy these requirements:
- One input parameter is a
&pavex::Error1. - They don't return a value2.
Other than that, you have a lot of freedom in how you define your error handlers:
- They can be free functions or methods.
- They can be synchronous or asynchronous.
- They can take one or more input parameters, leaning on Pavex's dependency injection system, as long as all injected inputs can be infallibly built.
Defining error observers
Use the #[error_observer] attribute to define a new error observer:
use pavex::error_observer;
use tracing_log_error::log_error;
#[error_observer]
pub async fn emit_error_log(e: &pavex::Error) {
log_error!(e, "An error occurred during request processing");
}
The signature of this error observer satisfies all the requirements listed in the previous section.
Registration
Invoke Blueprint::error_observer to register error observers for your application:
use crate::logger::EMIT_ERROR_LOG;
use pavex::Blueprint;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.error_observer(EMIT_ERROR_LOG); // (1)!
// [...]
}
EMIT_ERROR_LOGis a strongly-typed constant generated by the#[error_observer]attribute on theemit_error_logfunction.
Check out the documentation on component ids for more details.
You can register as many error observers as you want: they'll all be called when an error occurs, in the order they were registered. They are invoked after the relevant error handler has been called, but before the response is sent back to the client.
Position matters
An error observer will only be invoked for errors that occur in a route or middleware that was registered after the error observer itself.
use crate::{
logger::EMIT_ERROR_LOG,
routes::{INDEX, LOGIN},
};
use pavex::Blueprint;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.route(INDEX); // (1)!
bp.error_observer(EMIT_ERROR_LOG);
bp.route(LOGIN); // (2)!
// [...]
}
emit_error_logwon't be invoked for errors that occur while processing requests to theINDEXroute, since it was registered before the error observer.emit_error_logwill be invoked for errors that occur while processing requests to theLOGINroute, since it was registered after the error observer.
Dependency injection
Error observers can take advantage of dependency injection.
use crate::RootSpan;
use pavex::error_observer;
#[error_observer]
pub async fn enrich_root_span(e: &pavex::Error, root_span: &RootSpan /* (1)! */) {
root_span.record("error.msg", tracing::field::display(e));
root_span.record("error.details", tracing::field::debug(e));
}
&RootSpanis injected into the error observer by the framework.
You must specify the dependencies of your error observer as input parameters in its function signature. Those inputs are going to be built and injected by the framework, according to the constructors you have registered.
Check out the dependency injection guide for more details on how the process works.
Strictly infallible
Just like error handlers, error observers can't be fallible—they can't return a Result.
It goes further than that, though: they can't depend on fallible components, neither directly nor indirectly.
This constraint is necessary to avoid infinite loops.
Consider this scenario: you register an error observer that depends on a type A, and A's constructor can fail.
Something fails in the request processing pipeline:
- You want to invoke the error observer: you need to build
A. - You invoke
A's constructor, but it fails!- You must now invoke the error observer on the error returned by
A's constructor. - But to invoke the error observer, you need
A!- You try to construct
Aagain to report the failure of constructingA...
- You try to construct
- You must now invoke the error observer on the error returned by
It never ends!
Pavex will detect this scenario and return an error during code generation, so that you don't end up
in an infinite loop at runtime.
-
pavex::Erroris an opaque error type—it's a wrapper around the actual error type returned by the component that failed.
It implements theErrortrait from the standard library, so you can use its methods to extract information about the error (e.g.source,DisplayandDebugrepresentations, etc.).
If you need to access the underlying error type, you can use theinner_refmethod and then try to downcast it. ↩ -
Or, if you prefer, they return the unit type,
(). ↩