use clap::Parser; use config::AppConfig; use db::cache::AppCache; use db::database::AppDatabase; use git::hook::GitServiceHooks; use slog::{Drain, OwnedKVList, Record}; use tokio::signal; use tokio_util::sync::CancellationToken; mod args; use args::HookArgs; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Load configuration let cfg = AppConfig::load(); // 2. Init slog logging let log_level = cfg.log_level().unwrap_or_else(|_| "info".to_string()); let log = build_slog_logger(&log_level); // 3. Connect to database let db = AppDatabase::init(&cfg).await?; slog::info!(log, "database connected"); // 4. Connect to Redis cache (also provides the cluster pool for hook queue) let cache = AppCache::init(&cfg).await?; slog::info!(log, "cache connected"); // 5. Parse CLI args let args = HookArgs::parse(); slog::info!(log, "git-hook worker starting"; "worker_id" => %args.worker_id.unwrap_or_else(|| "default".to_string()) ); // 5. Build HTTP client for webhook delivery let http = reqwest::Client::builder() .user_agent("Code-Git-Hook/1.0") .build() .unwrap_or_else(|_| reqwest::Client::new()); // 6. Build and run git hook service let hooks = GitServiceHooks::new( db, cache.clone(), cache.redis_pool().clone(), log.clone(), cfg, std::sync::Arc::new(http), ); let cancel = CancellationToken::new(); let cancel_clone = cancel.clone(); // Spawn signal handler let log_clone = log.clone(); tokio::spawn(async move { let ctrl_c = async { signal::ctrl_c() .await .expect("failed to install CTRL+C handler"); }; #[cfg(unix)] let term = async { use tokio::signal::unix::{SignalKind, signal}; let mut sig = signal(SignalKind::terminate()).expect("failed to install SIGTERM handler"); sig.recv().await; }; #[cfg(not(unix))] let term = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { slog::info!(log_clone, "received SIGINT, initiating shutdown"); } _ = term => { slog::info!(log_clone, "received SIGTERM, initiating shutdown"); } } cancel_clone.cancel(); }); hooks.run(cancel).await?; slog::info!(log, "git-hook worker stopped"); Ok(()) } fn build_slog_logger(level: &str) -> slog::Logger { let level_filter = match level { "trace" => 0usize, "debug" => 1usize, "info" => 2usize, "warn" => 3usize, "error" => 4usize, _ => 2usize, }; struct StderrDrain(usize); impl Drain for StderrDrain { type Ok = (); type Err = (); #[inline] fn log(&self, record: &Record, _logger: &OwnedKVList) -> Result<(), ()> { let slog_level = match record.level() { slog::Level::Trace => 0, slog::Level::Debug => 1, slog::Level::Info => 2, slog::Level::Warning => 3, slog::Level::Error => 4, slog::Level::Critical => 5, }; if slog_level < self.0 { return Ok(()); } let _ = eprintln!( "{} [{}] {}:{} - {}", chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ"), record.level().to_string(), record .file() .rsplit_once('/') .map(|(_, s)| s) .unwrap_or(record.file()), record.line(), record.msg(), ); Ok(()) } } let drain = StderrDrain(level_filter); let drain = std::sync::Mutex::new(drain); let drain = slog::Fuse::new(drain); slog::Logger::root(drain, slog::o!()) }