Skip to content

Commit bcaeea2

Browse files
fix(readiness endpoint): return proper HTTP status codes based on health (loco-rs#1738)
* fix(readiness endpoint): return proper HTTP status codes based on health If `/_readiness` would return `{ "ok": false }`, it now also returns with a 500 status code so standard uptime monitors can take appropriate action. fixes loco-rs#1240 (loco-rs#1240 (comment) specifically) * Return 503 instead of 500 Doing this on my phone bc I'm not at my computer, hopefully I didn't miss anything --------- Co-authored-by: Elad Kaplan <kaplan.elad@gmail.com>
1 parent 90bcb4b commit bcaeea2

File tree

1 file changed

+30
-10
lines changed

1 file changed

+30
-10
lines changed

src/controller/monitoring.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ use super::{format, routes::Routes};
66
#[cfg(any(feature = "cache_inmem", feature = "cache_redis"))]
77
use crate::config;
88
use crate::{app::AppContext, Result};
9-
use axum::{extract::State, response::Response, routing::get};
9+
use axum::{
10+
extract::State,
11+
http::StatusCode,
12+
response::{IntoResponse, Response},
13+
routing::get,
14+
};
1015
use serde::Serialize;
1116

1217
/// Represents the health status of the application.
@@ -36,19 +41,25 @@ pub async fn health() -> Result<Response> {
3641
///
3742
/// # Errors
3843
/// All errors are logged, and the readiness status is returned as a JSON response.
39-
pub async fn readiness(State(ctx): State<AppContext>) -> Result<Response> {
44+
pub async fn readiness(State(ctx): State<AppContext>) -> (StatusCode, Response) {
4045
// Check database connection
4146
#[cfg(feature = "with-db")]
4247
if let Err(error) = &ctx.db.ping().await {
4348
tracing::error!(err.msg = %error, err.detail = ?error, "readiness_db_ping_error");
44-
return format::json(Health { ok: false });
49+
return (
50+
StatusCode::SERVICE_UNAVAILABLE,
51+
format::json(Health { ok: false }).into_response(),
52+
);
4553
}
4654

4755
// Check queue connection
4856
if let Some(queue) = &ctx.queue_provider {
4957
if let Err(error) = queue.ping().await {
5058
tracing::error!(err.msg = %error, err.detail = ?error, "readiness_queue_ping_error");
51-
return format::json(Health { ok: false });
59+
return (
60+
StatusCode::SERVICE_UNAVAILABLE,
61+
format::json(Health { ok: false }).into_response(),
62+
);
5263
}
5364
}
5465

@@ -60,21 +71,30 @@ pub async fn readiness(State(ctx): State<AppContext>) -> Result<Response> {
6071
config::CacheConfig::InMem(_) => {
6172
if let Err(error) = &ctx.cache.driver.ping().await {
6273
tracing::error!(err.msg = %error, err.detail = ?error, "readiness_cache_ping_error");
63-
return format::json(Health { ok: false });
74+
return (
75+
StatusCode::SERVICE_UNAVAILABLE,
76+
format::json(Health { ok: false }).into_response(),
77+
);
6478
}
6579
}
6680
#[cfg(feature = "cache_redis")]
6781
config::CacheConfig::Redis(_) => {
6882
if let Err(error) = &ctx.cache.driver.ping().await {
6983
tracing::error!(err.msg = %error, err.detail = ?error, "readiness_cache_ping_error");
70-
return format::json(Health { ok: false });
84+
return (
85+
StatusCode::SERVICE_UNAVAILABLE,
86+
format::json(Health { ok: false }).into_response(),
87+
);
7188
}
7289
}
7390
config::CacheConfig::Null => (),
7491
}
7592
}
7693

77-
format::json(Health { ok: true })
94+
(
95+
StatusCode::OK,
96+
format::json(Health { ok: true }).into_response(),
97+
)
7898
}
7999

80100
/// Defines and returns the readiness-related routes.
@@ -230,7 +250,7 @@ mod tests {
230250

231251
// Test the router directly using oneshot
232252
let response = router.oneshot(req).await.unwrap();
233-
assert_eq!(response.status(), 200);
253+
assert_eq!(response.status(), 503);
234254

235255
// Get the response body
236256
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
@@ -346,7 +366,7 @@ mod tests {
346366

347367
// Test the router directly using oneshot
348368
let response = router.oneshot(req).await.unwrap();
349-
assert_eq!(response.status(), 200);
369+
assert_eq!(response.status(), 503);
350370

351371
// Get the response body
352372
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
@@ -428,7 +448,7 @@ mod tests {
428448

429449
// Test the router directly using oneshot
430450
let response = router.oneshot(req).await.unwrap();
431-
assert_eq!(response.status(), 200);
451+
assert_eq!(response.status(), 503);
432452

433453
// Get the response body
434454
let body = axum::body::to_bytes(response.into_body(), usize::MAX)

0 commit comments

Comments
 (0)