mod configs; mod middlewares; mod routes; mod tasks; use axum::Router; use database::{ConnectOptions, get_connection}; use tracing::{debug, info}; use tracing_subscriber::fmt::format::{DefaultFields, Format}; use crate::configs::{ProgramSettings, get_program_settings, logging::LoggingSettings}; #[tokio::main] async fn main() { // Temporary subscriber for initial logging during configuration reading let make_temporary_subscriber = || { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_target(false) .with_level(true) .finish() }; let settings = tracing::subscriber::with_default(make_temporary_subscriber(), || -> ProgramSettings { debug!("Temporary subscriber installed."); info!("Reading configuration..."); let settings = get_program_settings(); info!("Configuration read successfully."); debug!("Resetting global subscriber..."); let subscriber = get_global_tracing_subscriber_builder(&settings.logging).finish(); tracing::subscriber::set_global_default(subscriber) .expect("Failed to set global default subscriber"); debug!( "Global subscriber set with logging level: {:?}", settings.logging.level ); settings }); tasks::startup::run_startup_tasks(&settings) .await .expect("Failed to run startup tasks"); // setup database connection pool info!("Establishing database connection..."); debug!("Database URL: {}", settings.database.url); let db_options = |options: &mut ConnectOptions| { options.max_connections(settings.database.max_connections); }; let db_connection = get_connection(&settings.database.url, Some(db_options)) .await .expect("Failed to establish database connection"); info!("Database connection established."); // build the axum app and run the server... info!("Starting application..."); let app: Router = routes::get_root_router(routes::AppState { database_connection: db_connection, service: std::sync::Arc::new(routes::AppService {}), }); let address = format!("{}:{}", settings.server.address, settings.server.port); info!("Starting server at http://{}", address); let listener = tokio::net::TcpListener::bind(address) .await .expect("Failed to bind to address"); axum::serve(listener, app) .await .expect("Failed to run the server"); } fn get_global_tracing_subscriber_builder( settings: &LoggingSettings, ) -> tracing_subscriber::fmt::SubscriberBuilder< DefaultFields, Format, > { // After configuration is read, install the global subscriber let builder = tracing_subscriber::fmt() .with_max_level(settings.level) .with_target(false) .with_level(true); if settings.utc { builder.with_timer(BoxedTimer(Box::new( tracing_subscriber::fmt::time::UtcTime::rfc_3339(), ))) } else { builder.with_timer(BoxedTimer(Box::new( tracing_subscriber::fmt::time::ChronoLocal::rfc_3339(), ))) } } // A small wrapper that holds a boxed `FormatTime` trait object and itself // implements `FormatTime`, allowing us to use it as a concrete type with // `builder.with_timer` while still picking the concrete timer implementation // at runtime. // wrapper type to hold boxed timers and implement the `FormatTime` trait for // a concrete type so `with_timer` may be called once outside the conditional. struct BoxedTimer(Box); impl tracing_subscriber::fmt::time::FormatTime for BoxedTimer { fn format_time( &self, w: &mut tracing_subscriber::fmt::format::Writer<'_>, ) -> std::result::Result<(), std::fmt::Error> { self.0.format_time(w) } }