examples: refactor error logging in error-handling example (#3527)

This commit is contained in:
Paul Mattern
2025-10-11 17:41:12 +02:00
committed by GitHub
parent dcbcf5c5fd
commit 8389ab10b0
+28 -11
View File
@@ -32,6 +32,7 @@ use std::{
use axum::{ use axum::{
extract::{rejection::JsonRejection, FromRequest, MatchedPath, Request, State}, extract::{rejection::JsonRejection, FromRequest, MatchedPath, Request, State},
http::StatusCode, http::StatusCode,
middleware::{from_fn, Next},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::post, routing::post,
Router, Router,
@@ -77,6 +78,7 @@ async fn main() {
// logging of errors so disable that // logging of errors so disable that
.on_failure(()), .on_failure(()),
) )
.layer(from_fn(log_app_errors))
.with_state(state); .with_state(state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") 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. // The kinds of errors we can hit in our application.
#[derive(Debug)]
enum AppError { enum AppError {
// The request body contained invalid JSON // The request body contained invalid JSON
JsonRejection(JsonRejection), JsonRejection(JsonRejection),
@@ -153,8 +156,6 @@ enum AppError {
} }
// Tell axum how `AppError` should be converted into a response. // Tell axum how `AppError` should be converted into a response.
//
// This is also a convenient place to log errors.
impl IntoResponse for AppError { impl IntoResponse for AppError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
// How we want errors responses to be serialized // How we want errors responses to be serialized
@@ -163,30 +164,36 @@ impl IntoResponse for AppError {
message: String, message: String,
} }
let (status, message) = match self { let (status, message, err) = match &self {
AppError::JsonRejection(rejection) => { AppError::JsonRejection(rejection) => {
// This error is caused by bad user input so don't log it // 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) => { AppError::TimeError(_err) => {
// Because `TraceLayer` wraps each request in a span that contains the request // While we could simply log the error here we would introduce
// method, uri, etc we don't need to include those details here // a side-effect to our conversion, instead add the AppError to
tracing::error!(%err, "error from time_library"); // the Response as an Extension
// Don't expose any details about the error to the client // Don't expose any details about the error to the client
( (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"Something went wrong".to_owned(), "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 { impl From<JsonRejection> for AppError {
fn from(rejection: JsonRejection) -> Self { fn from(rejection: JsonRejection) -> Self {
// Arc enables cloning of a JsonRejection
Self::JsonRejection(rejection) 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 // Imagine this is some third party library that we're using. It sometimes returns errors which we
// want to log. // want to log.
mod time_library { mod time_library {
@@ -220,7 +237,7 @@ mod time_library {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Error { pub enum Error {
FailedToGetTime, FailedToGetTime,
} }