mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 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::{
|
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,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user