use std::net::SocketAddr;
use std::path::PathBuf;
use clap::Parser;
use tracing_subscriber::EnvFilter;
use logdive_api::router::build_router;
use logdive_api::state::AppState;
use logdive_core::{LogdiveError, Result, db_path};
#[derive(Parser, Debug)]
#[command(
name = "logdive-api",
version,
about = "Read-only HTTP API server for a logdive index",
long_about = None,
)]
struct Cli {
#[arg(long, value_name = "PATH", env = "LOGDIVE_DB")]
db: Option<PathBuf>,
#[arg(long, default_value_t = 4000, env = "LOGDIVE_API_PORT")]
port: u16,
#[arg(long, default_value = "127.0.0.1", env = "LOGDIVE_API_HOST")]
host: String,
}
#[tokio::main]
async fn main() -> Result<()> {
init_tracing();
let cli = Cli::parse();
let db = db_path(cli.db.as_deref());
if !db.exists() {
let msg = format!(
"no index found at {}; run `logdive ingest` to create one first",
db.display()
);
return Err(LogdiveError::io_at(
&db,
std::io::Error::new(std::io::ErrorKind::NotFound, msg),
));
}
let state = AppState::new(db.clone());
let app = build_router(state);
let addr: SocketAddr =
format!("{}:{}", cli.host, cli.port)
.parse()
.map_err(|e: std::net::AddrParseError| {
LogdiveError::io_at(
&db,
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("invalid host:port `{}:{}`: {e}", cli.host, cli.port),
),
)
})?;
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| LogdiveError::io_at(&db, e))?;
let bound = listener
.local_addr()
.map_err(|e| LogdiveError::io_at(&db, e))?;
tracing::info!(%bound, index = %db.display(), "logdive-api listening");
eprintln!(
"logdive-api listening on http://{bound} (index: {})",
db.display()
);
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(|e| LogdiveError::io_at(&db, e))?;
tracing::info!("logdive-api shutdown complete");
Ok(())
}
fn init_tracing() {
let filter = EnvFilter::try_from_env("LOGDIVE_LOG").unwrap_or_else(|_| EnvFilter::new("warn"));
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.init();
}
async fn shutdown_signal() {
let ctrl_c = async {
if let Err(e) = tokio::signal::ctrl_c().await {
tracing::warn!(error = %e, "failed to install Ctrl-C handler");
std::future::pending::<()>().await;
}
};
#[cfg(unix)]
let terminate = async {
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
Ok(mut stream) => {
stream.recv().await;
}
Err(e) => {
tracing::warn!(error = %e, "failed to install SIGTERM handler");
std::future::pending::<()>().await;
}
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
tracing::info!("Ctrl-C received, shutting down");
}
_ = terminate => {
tracing::info!("SIGTERM received, shutting down");
}
}
}