use std::sync::Arc; use axum::Router; use clap::Command; use database::get_connection; use sea_orm::ConnectOptions; use tracing::{debug, info}; use tracing_subscriber::fmt::format::{DefaultFields, Format}; use crate::{ cmd::CliCommand, configs::{ProgramSettings, get_program_settings, logging::LoggingSettings}, log, routes::{self, AppService, AppState}, services::settings::SettingsService, tasks, }; pub fn get_cli_command() -> CliCommand { CliCommand { command: command(), action, } } fn command() -> Command { Command::new("start").about("Start the server") } fn action( _matches: &clap::ArgMatches, ) -> std::pin::Pin + Send>> { Box::pin(async move {}) } pub async fn start_server() { let settings = tracing::subscriber::with_default( log::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 = Arc::new( 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(Arc::new(get_app_state(&db_connection))); 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(), ))) } } fn get_app_state(db_connection: &Arc) -> AppState { AppState { database_connection: db_connection.clone(), service: Arc::new(AppService { settings: Arc::new(SettingsService::new(db_connection.clone())), }), } } // 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) } }