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::{
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,
}