use std::{collections::HashMap, sync::Arc, time::Duration}; use axum::extract::MatchedPath; use color_eyre::eyre::{Error, Result}; use hyper::{Body, Request, Response}; use post::Post; use tag::Tag; use tera::Tera; use tower_http::{compression::CompressionLayer, cors::CorsLayer}; use tracing::{info_span, log::*, Span}; use tracing_subscriber::{prelude::*, EnvFilter}; mod handlers; mod post; mod tag; mod feed; pub struct AppState { base_url: String, posts: HashMap, tags: HashMap, tera: Tera, } #[tokio::main] async fn main() -> Result<()> { color_eyre::install()?; init_tracing(); info!("Starting server..."); let base_url = option_env!("SITE_BASE_URL").unwrap_or("http://localhost:8180").to_string(); let tera = Tera::new("templates/**/*")?; let posts = post::load_all().await?; let tags = tag::get_tags(posts.values()); let state = Arc::new(AppState { base_url, tera, posts, tags }); let app = handlers::routes(&state) .layer(CorsLayer::permissive()) .layer(CompressionLayer::new()) .layer( tower_http::trace::TraceLayer::new_for_http() .make_span_with(make_span) .on_response(on_response), ) .with_state(state); info!("Now listening at http://localhost:8180"); axum::Server::bind(&"0.0.0.0:8180".parse().unwrap()) .serve(app.into_make_service()) .await?; Ok(()) } fn init_tracing() { let filter = EnvFilter::builder() .with_default_directive("into".parse().unwrap()) .from_env_lossy(); tracing_subscriber::registry() .with(filter) .with(tracing_subscriber::fmt::layer()) .init(); } fn make_span(request: &Request) -> Span { let uri = request.uri(); let route = request .extensions() .get::() .map(|mp| mp.as_str()) .unwrap_or_default(); let method = request.method().as_str(); let target = uri.path_and_query().map(|p| p.as_str()).unwrap_or_default(); let name = format!("{method} {route}"); use tracing::field::Empty; info_span!( "request", otel.name = %name, http.route = %route, http.method = %method, http.target = %target, http.status_code = Empty ) } fn on_response(response: &Response, _latency: Duration, span: &Span) { span.record("http.status_code", response.status().as_str()); } #[derive(Debug)] pub enum WebsiteError { NotFound, InternalError(Error), } impl From for WebsiteError where E: Into, { fn from(value: E) -> Self { WebsiteError::InternalError(value.into()) } }