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