use clap::Parser; use config::AppConfig; use service::AppService; use slog::{Drain, OwnedKVList, Record}; #[derive(Parser, Debug)] #[command(name = "email-worker")] #[command(version)] struct Args { #[arg(long, default_value = "info")] log_level: String, } #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); let cfg = AppConfig::load(); let log = build_logger(&args.log_level); slog::info!(log, "Starting email worker"); let service = AppService::new(cfg).await?; let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel::<()>(1); let log_for_signal = log.clone(); tokio::spawn(async move { tokio::signal::ctrl_c().await.ok(); slog::info!(log_for_signal, "shutting down email worker"); let _ = shutdown_tx.send(()); }); service.start_email_workers(shutdown_rx).await?; slog::info!(log, "email worker stopped"); Ok(()) } fn build_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!()) }