autoconnect_web/
dockerflow.rs

1//! Health and Dockerflow routes
2use std::thread;
3
4use actix_web::{
5    web::{self, Data, Json},
6    HttpResponse, ResponseError,
7};
8use serde_json::json;
9
10use autoconnect_settings::AppState;
11// The following two items are used by "reliable_report"
12// Feature flagging them causes a cascade of other unused imports for some configurations.
13#[allow(unused_imports)]
14use autopush_common::metric_name::MetricName;
15#[allow(unused_imports)]
16use autopush_common::metrics::StatsdClientExt;
17
18use crate::error::ApiError;
19
20/// Configure the Dockerflow (and legacy monitoring) routes
21pub fn config(config: &mut web::ServiceConfig) {
22    config
23        .service(web::resource("/status").route(web::get().to(status_route)))
24        .service(web::resource("/health").route(web::get().to(health_route)))
25        .service(web::resource("/v1/err/crit").route(web::get().to(log_check)))
26        // standardized
27        .service(web::resource("/__error__").route(web::get().to(log_check)))
28        // Dockerflow
29        .service(web::resource("/__heartbeat__").route(web::get().to(health_route)))
30        .service(web::resource("/__lbheartbeat__").route(web::get().to(lb_heartbeat_route)))
31        .service(web::resource("/__version__").route(web::get().to(version_route)));
32}
33
34/// Handle the `/health` and `/__heartbeat__` routes
35pub async fn health_route(state: Data<AppState>) -> Json<serde_json::Value> {
36    #[allow(unused_mut)]
37    let mut health = json!({
38        "status": if state
39        .db
40        .health_check()
41        .await
42        .map_err(|e| {
43            error!("Autoconnect Health Error: {:?}", e);
44            e
45        })
46        .is_ok() { "OK" } else {"ERROR"},
47        "version": env!("CARGO_PKG_VERSION"),
48        "connections": state.clients.count().await
49    });
50
51    #[cfg(feature = "reliable_report")]
52    {
53        health["reliability"] = json!(state.reliability.health_check().await.unwrap_or_else(|e| {
54            state
55                .metrics
56                .incr_with_tags(MetricName::ErrorRedisUnavailable)
57                .with_tag("application", "autoconnect")
58                .send();
59            error!("🔍🟥 Reliability reporting down: {:?}", e);
60            "ERROR"
61        }));
62    }
63
64    Json(health)
65}
66
67/// Handle the `/status` route
68pub async fn status_route(state: Data<AppState>) -> Json<serde_json::Value> {
69    let mut status: std::collections::HashMap<&str, String> = std::collections::HashMap::new();
70    status.insert("version", env!("CARGO_PKG_VERSION").to_owned());
71    let check = state.db.health_check().await;
72    if check.is_ok() {
73        status.insert("status", "OK".to_owned());
74    } else {
75        status.insert("status", "ERROR".to_owned());
76    }
77    if let Some(err) = check.err().map(|v| v.to_string()) {
78        status.insert("error", err);
79    };
80
81    Json(json!(status))
82}
83
84/// Handle the `/__lbheartbeat__` route
85pub async fn lb_heartbeat_route() -> HttpResponse {
86    // Used by the load balancers, just return OK.
87    HttpResponse::Ok().finish()
88}
89
90/// Handle the `/__version__` route
91pub async fn version_route() -> HttpResponse {
92    // Return the contents of the version.json file created by circleci
93    // and stored in the docker root
94    HttpResponse::Ok()
95        .content_type("application/json")
96        .body(include_str!("../../../version.json"))
97}
98
99/// Handle the `/v1/err` route
100pub async fn log_check() -> Result<HttpResponse, ApiError> {
101    let err = ApiError::LogCheck;
102    error!(
103        "Test Critical Message";
104        "status_code" => err.status_code().as_u16(),
105        "errno" => err.errno(),
106    );
107
108    thread::spawn(|| {
109        panic!("LogCheck");
110    });
111
112    Err(err)
113}