use axum::{
middleware::{self},
routing::{get, post},
Router,
};
use sqlx::postgres::PgPoolOptions;
use std::{net::SocketAddr, sync::Arc};
use tokio::sync::mpsc;
use tower_http::trace::TraceLayer;
use crate::http::common::healthz;
use crate::http::{
auth::{auth, rate_limit},
metrics::{self, metrics_worker, post_metrics_batch, MetricsBatchRequest},
ops::get_rate_limit_stats,
projects::get_projects,
traces,
};
use crate::ingest::batch::{ingest_worker, post_batch, BatchIngestRequest};
use crate::ingest::otlp;
use crate::state::{AppState, RateLimitStats, ServerConfig};
pub async fn run_server(config: ServerConfig) -> anyhow::Result<()> {
let pool = PgPoolOptions::new()
.max_connections(20)
.connect(&config.database_url)
.await?;
sqlx::migrate!("./migrations").run(&pool).await?;
let (ingest_tx, ingest_rx) = mpsc::channel::<BatchIngestRequest>(1000);
let (metrics_tx, metrics_rx) = mpsc::channel::<MetricsBatchRequest>(5000);
let qps = config.rate_limit_qps;
let burst = config.rate_limit_burst;
let query_limiter = AppState::build_limiter(qps, burst);
let rate_limit_stats = Arc::new(RateLimitStats::new());
let state = AppState {
pool,
api_bearer_token: Arc::from(config.api_bearer_token),
langfuse_public_key: config.langfuse_public_key.map(Arc::from),
langfuse_secret_key: config.langfuse_secret_key.map(Arc::from),
default_project_id: Arc::from(config.default_project_id),
ingest_tx,
metrics_tx,
query_limiter,
rate_limit_stats,
rate_limit_qps: qps,
rate_limit_burst: burst,
};
tokio::spawn(ingest_worker(
state.pool.clone(),
state.default_project_id.clone(),
ingest_rx,
));
tokio::spawn(metrics_worker(
state.pool.clone(),
state.default_project_id.clone(),
metrics_rx,
));
let query_routes = Router::new()
.route("/api/public/metrics/daily", get(metrics::get_metrics_daily))
.route("/api/public/metrics/query", get(metrics::get_metrics_query))
.route("/api/public/metrics/names", get(metrics::get_metrics_names))
.route("/api/public/traces", get(traces::get_traces))
.route("/api/public/traces/:traceId", get(traces::get_trace))
.route_layer(middleware::from_fn_with_state(state.clone(), rate_limit));
let write_routes = Router::new()
.route("/v1/l/batch", post(post_batch))
.route("/v1/metrics/batch", post(post_metrics_batch))
.route("/api/public/projects", get(get_projects))
.route("/api/public/otel/v1/traces", post(otlp::post_otel_traces));
let protected_routes = Router::new()
.merge(query_routes)
.merge(write_routes)
.route_layer(middleware::from_fn_with_state(state.clone(), auth));
let addr: SocketAddr = config.bind_addr.parse()?;
tracing::info!(
"listening on {} (rate_limit: {} qps, burst {})",
addr,
qps,
burst
);
let app = Router::new()
.route("/healthz", get(healthz))
.route("/api/internal/rate_limit_stats", get(get_rate_limit_stats))
.merge(protected_routes)
.with_state(state)
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}