Health checks

Roadster allows registering HealthChecks to ensure server instances are functioning as expected. Roadster provides some default health checks that simply check that the server's dependencies (e.g., DB and Redis) are accessible. All health checks -- both the defaults and any custom ones registered for an app -- run on app startup, via the CLI, and in the API at /api/_health. The route of this API is configurable via the service.http.default-routes.health.route config field.

Roadster also provides the /api/_ping API, which simply returns a successful HTTP status (200) and does no other work.

Custom HealthCheck

To provide a custom health check, implement the HealthCheck trait and register the check when building the app. Note that if the check requires access to the app's state, it should be provided via a Weak reference to the state. This is because health checks are stored in Roadster's AppContext, which introduces a circular reference between the context and health checks. A weak reference to AppContext can be retrieved via AppContext#downgrade.

Implement HealthCheck

pub struct ExampleCheck {
    state: AppContextWeak,
}

impl ExampleCheck {
    pub fn new(state: &AppContext) -> Self {
        Self {
            state: state.downgrade(),
        }
    }
}

#[async_trait]
impl HealthCheck for ExampleCheck {
    fn name(&self) -> String {
        "example".to_string()
    }

    fn enabled(&self) -> bool {
        // Custom health checks can be enabled/disabled via the app config
        // just like built-in checks
        if let Some(state) = self.state.upgrade() {
            state
                .config()
                .health_check
                .custom
                .get(&self.name())
                .map(|config| config.common.enabled(&state))
                .unwrap_or_else(|| state.config().health_check.default_enable)
        } else {
            false
        }
    }

    async fn check(&self) -> RoadsterResult<CheckResponse> {
        Ok(CheckResponse::builder()
            .status(Status::Ok)
            .latency(Duration::from_secs(0))
            .build())
    }
}

Register custom HealthCheck

fn build_app() -> RoadsterApp<AppContext> {
    RoadsterApp::builder()
        // Use the default `AppContext` for this example
        .state_provider(|context| Ok(context))
        // Register custom health check(s)
        .add_health_check_provider(|registry, state| {
            registry.register(ExampleCheck::new(state))?;
            Ok(())
        })
        .add_service_provider(move |_registry, _state| {
            Box::pin(async move { todo!("Add services here.") })
        })
        .build()
}