mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-16 00:00:18 +02:00
examples: refactor error logging in error-handling example (#3527)
This commit is contained in:
@@ -32,6 +32,7 @@ use std::{
|
||||
use axum::{
|
||||
extract::{rejection::JsonRejection, FromRequest, MatchedPath, Request, State},
|
||||
http::StatusCode,
|
||||
middleware::{from_fn, Next},
|
||||
response::{IntoResponse, Response},
|
||||
routing::post,
|
||||
Router,
|
||||
@@ -77,6 +78,7 @@ async fn main() {
|
||||
// logging of errors so disable that
|
||||
.on_failure(()),
|
||||
)
|
||||
.layer(from_fn(log_app_errors))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||
@@ -145,6 +147,7 @@ where
|
||||
}
|
||||
|
||||
// The kinds of errors we can hit in our application.
|
||||
#[derive(Debug)]
|
||||
enum AppError {
|
||||
// The request body contained invalid JSON
|
||||
JsonRejection(JsonRejection),
|
||||
@@ -153,8 +156,6 @@ enum AppError {
|
||||
}
|
||||
|
||||
// Tell axum how `AppError` should be converted into a response.
|
||||
//
|
||||
// This is also a convenient place to log errors.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
// How we want errors responses to be serialized
|
||||
@@ -163,30 +164,36 @@ impl IntoResponse for AppError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
let (status, message) = match self {
|
||||
let (status, message, err) = match &self {
|
||||
AppError::JsonRejection(rejection) => {
|
||||
// This error is caused by bad user input so don't log it
|
||||
(rejection.status(), rejection.body_text())
|
||||
(rejection.status(), rejection.body_text(), None)
|
||||
}
|
||||
AppError::TimeError(err) => {
|
||||
// Because `TraceLayer` wraps each request in a span that contains the request
|
||||
// method, uri, etc we don't need to include those details here
|
||||
tracing::error!(%err, "error from time_library");
|
||||
|
||||
AppError::TimeError(_err) => {
|
||||
// While we could simply log the error here we would introduce
|
||||
// a side-effect to our conversion, instead add the AppError to
|
||||
// the Response as an Extension
|
||||
// Don't expose any details about the error to the client
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Something went wrong".to_owned(),
|
||||
Some(self),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
(status, AppJson(ErrorResponse { message })).into_response()
|
||||
let mut response = (status, AppJson(ErrorResponse { message })).into_response();
|
||||
if let Some(err) = err {
|
||||
// Insert our error into the response, our logging middleware will use this.
|
||||
response.extensions_mut().insert(Arc::new(err));
|
||||
}
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonRejection> for AppError {
|
||||
fn from(rejection: JsonRejection) -> Self {
|
||||
// Arc enables cloning of a JsonRejection
|
||||
Self::JsonRejection(rejection)
|
||||
}
|
||||
}
|
||||
@@ -197,6 +204,16 @@ impl From<time_library::Error> for AppError {
|
||||
}
|
||||
}
|
||||
|
||||
// Our middleware is responsible for logging error details internally
|
||||
async fn log_app_errors(request: Request, next: Next) -> Response {
|
||||
let response = next.run(request).await;
|
||||
// If the response contains an AppError Extension, log it.
|
||||
if let Some(err) = response.extensions().get::<Arc<AppError>>() {
|
||||
tracing::error!(?err, "an unexpected error occurred inside a handler");
|
||||
}
|
||||
response
|
||||
}
|
||||
|
||||
// Imagine this is some third party library that we're using. It sometimes returns errors which we
|
||||
// want to log.
|
||||
mod time_library {
|
||||
@@ -220,7 +237,7 @@ mod time_library {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
FailedToGetTime,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user