use std::sync::Arc;
use anyhow::Context;
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
use rust_job_queue_api_worker_system::{
api::{build_router, AppState},
connect, migrate, PoolConfig,
};
use tokio::net::TcpListener;
use tokio::signal;
use tracing::info;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
init_tracing();
let database_url = std::env::var("DATABASE_URL").context("DATABASE_URL must be set")?;
let bind_addr = std::env::var("API_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".into());
let pool = connect(&PoolConfig::from_url(database_url)).await?;
migrate(&pool).await?;
let metrics_handle = init_metrics()?;
let state = AppState {
pool,
metrics: Arc::new(metrics_handle),
};
let app = build_router(state);
let listener = TcpListener::bind(&bind_addr).await?;
let local_addr = listener.local_addr()?;
info!(%local_addr, "api listening");
axum::serve(listener, app)
.with_graceful_shutdown(wait_shutdown())
.await?;
info!("api exiting");
Ok(())
}
fn init_tracing() {
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,sqlx=warn"));
let json = std::env::var("RUST_LOG_FORMAT").as_deref().unwrap_or("") == "json";
let builder = tracing_subscriber::fmt().with_env_filter(filter);
if json {
builder.json().init();
} else {
builder.init();
}
}
fn init_metrics() -> anyhow::Result<PrometheusHandle> {
let recorder = PrometheusBuilder::new().build_recorder();
let handle = recorder.handle();
metrics::set_global_recorder(recorder)
.map_err(|e| anyhow::anyhow!("failed to install prometheus recorder: {e}"))?;
Ok(handle)
}
async fn wait_shutdown() {
let ctrl_c = async {
let _ = signal::ctrl_c().await;
};
#[cfg(unix)]
let term = async {
let mut sig = signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("install sigterm handler");
sig.recv().await;
};
#[cfg(not(unix))]
let term = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {}
() = term => {}
}
}