diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index 997657e6..50fe40de 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono" -version = "0.12.0" +version = "0.12.1" edition = "2021" authors = ["V. Ageno "] description = "The react/rust fullstack framework" diff --git a/crates/tuono_lib/Cargo.toml b/crates/tuono_lib/Cargo.toml index 1f47d3fe..e8b75475 100644 --- a/crates/tuono_lib/Cargo.toml +++ b/crates/tuono_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono_lib" -version = "0.12.0" +version = "0.12.1" edition = "2021" authors = ["V. Ageno "] description = "The react/rust fullstack framework" @@ -33,9 +33,12 @@ either = "1.13.0" tower-http = {version = "0.6.0", features = ["fs"]} colored = "2.1.0" -tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.12.0"} +tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.12.1"} # Match the same version used by axum tokio-tungstenite = "0.24.0" futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } tungstenite = "0.24.0" +http = "1.1.0" +pin-project = "1.1.7" +tower = "0.5.1" diff --git a/crates/tuono_lib/src/catch_all.rs b/crates/tuono_lib/src/catch_all.rs index 8b983c95..39b8a1c9 100644 --- a/crates/tuono_lib/src/catch_all.rs +++ b/crates/tuono_lib/src/catch_all.rs @@ -1,15 +1,12 @@ use crate::{ssr::Js, Payload}; use axum::extract::{Path, Request}; use axum::response::Html; -use colored::*; use std::collections::HashMap; -use tokio::time::Instant; pub async fn catch_all( Path(params): Path>, request: Request, ) -> Html { - let start = Instant::now(); let pathname = request.uri(); let headers = request.headers(); @@ -20,18 +17,6 @@ pub async fn catch_all( let result = Js::render_to_string(Some(&payload)); - let duration = start.elapsed(); - - // TODO: handle 404 error on catch_all route - let http_code = "200"; - - println!( - " GET {} {} in {}ms", - req.uri.path(), - http_code.green(), - duration.as_millis() - ); - match result { Ok(html) => Html(html), _ => Html("500 internal server error".to_string()), diff --git a/crates/tuono_lib/src/lib.rs b/crates/tuono_lib/src/lib.rs index 067533ed..6dc388cb 100644 --- a/crates/tuono_lib/src/lib.rs +++ b/crates/tuono_lib/src/lib.rs @@ -5,6 +5,7 @@ mod payload; mod request; mod response; mod server; +mod services; mod ssr; mod vite_reverse_proxy; mod vite_websocket_proxy; diff --git a/crates/tuono_lib/src/response.rs b/crates/tuono_lib/src/response.rs index 054e6e25..5e43c1ac 100644 --- a/crates/tuono_lib/src/response.rs +++ b/crates/tuono_lib/src/response.rs @@ -3,9 +3,7 @@ use crate::{ssr::Js, Payload}; use axum::http::{HeaderMap, StatusCode}; use axum::response::{Html, IntoResponse, Redirect}; use axum::Json; -use colored::*; use erased_serde::Serialize; -use tokio::time::Instant; pub struct Props { data: Box, @@ -74,25 +72,13 @@ impl Response { pub fn render_to_string(&self, req: Request) -> impl IntoResponse { match self { Self::Props(Props { data, http_code }) => { - let start = Instant::now(); let payload = Payload::new(&req, data).client_payload().unwrap(); - let response = match Js::render_to_string(Some(&payload)) { + match Js::render_to_string(Some(&payload)) { Ok(html) => (*http_code, Html(html)), Err(_) => (*http_code, Html("500 Internal server error".to_string())), } - .into_response(); - - let duration = start.elapsed(); - - println!( - " GET {} {} in {}ms", - req.uri.path(), - http_code.as_str().green(), - duration.as_millis() - ); - - response + .into_response() } Self::Redirect(to) => Redirect::permanent(to).into_response(), Self::Custom(response) => response.clone().into_response(), diff --git a/crates/tuono_lib/src/server.rs b/crates/tuono_lib/src/server.rs index 052becdf..1928c67a 100644 --- a/crates/tuono_lib/src/server.rs +++ b/crates/tuono_lib/src/server.rs @@ -1,6 +1,6 @@ -use crate::mode::{Mode, GLOBAL_MODE}; - use crate::manifest::load_manifest; +use crate::mode::{Mode, GLOBAL_MODE}; +use crate::services::logger::LoggerLayer; use axum::routing::{get, Router}; use colored::Colorize; use ssr_rs::Ssr; @@ -42,9 +42,13 @@ impl Server { let router = self .router .to_owned() + .layer(LoggerLayer::new()) .route("/vite-server/", get(vite_websocket_proxy)) .route("/vite-server/*path", get(vite_reverse_proxy)) - .fallback_service(ServeDir::new(DEV_PUBLIC_DIR).fallback(get(catch_all))) + .fallback_service( + ServeDir::new(DEV_PUBLIC_DIR) + .fallback(get(catch_all).layer(LoggerLayer::new())), + ) .with_state(fetch); axum::serve(listener, router) @@ -58,7 +62,11 @@ impl Server { let router = self .router .to_owned() - .fallback_service(ServeDir::new(PROD_PUBLIC_DIR).fallback(get(catch_all))) + .layer(LoggerLayer::new()) + .fallback_service( + ServeDir::new(PROD_PUBLIC_DIR) + .fallback(get(catch_all).layer(LoggerLayer::new())), + ) .with_state(fetch); axum::serve(listener, router) diff --git a/crates/tuono_lib/src/services.rs b/crates/tuono_lib/src/services.rs new file mode 100644 index 00000000..d9917284 --- /dev/null +++ b/crates/tuono_lib/src/services.rs @@ -0,0 +1 @@ +pub mod logger; diff --git a/crates/tuono_lib/src/services/logger.rs b/crates/tuono_lib/src/services/logger.rs new file mode 100644 index 00000000..77555f0d --- /dev/null +++ b/crates/tuono_lib/src/services/logger.rs @@ -0,0 +1,106 @@ +use colored::Colorize; +use http::{method::Method, Request, Response}; +use pin_project::pin_project; +use std::fmt::Debug; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::time::Instant; +use tower::{Layer, Service}; + +#[derive(Clone)] +pub struct LoggerLayer {} + +impl LoggerLayer { + pub fn new() -> Self { + LoggerLayer {} + } +} + +impl Layer for LoggerLayer { + type Service = Logger; + + fn layer(&self, inner: S) -> Self::Service { + Logger::new(inner) + } +} + +#[derive(Clone)] +pub struct Logger { + inner: S, +} + +impl Logger { + pub fn new(inner: S) -> Self { + Logger { inner } + } +} + +impl Service> for Logger +where + S: Service, Response = Response>, + ResBody: Default, + >>::Error: Debug, +{ + type Response = S::Response; + type Error = S::Error; + type Future = LoggerFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let method = req.method().clone(); + let path = req.uri().path().to_string(); + + LoggerFuture { + future: self.inner.call(req), + method, + path, + start: Instant::now(), + } + } +} + +#[pin_project] +pub struct LoggerFuture { + #[pin] + future: F, + method: Method, + path: String, + start: Instant, +} + +impl Future for LoggerFuture +where + F: Future, E>>, + B: Default, + E: Debug, +{ + type Output = Result, E>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res: F::Output = match this.future.poll(cx) { + Poll::Ready(res) => res, + Poll::Pending => return Poll::Pending, + }; + + if this.path.starts_with("/__tuono/data") { + return Poll::Ready(res); + } + + let status_code = res.as_ref().unwrap().status(); + + println!( + " {} {} {} in {}ms", + this.method, + this.path, + status_code.as_str().green(), + this.start.elapsed().as_millis() + ); + + Poll::Ready(res) + } +} diff --git a/crates/tuono_lib_macros/Cargo.toml b/crates/tuono_lib_macros/Cargo.toml index f16bef70..07c1e3fb 100644 --- a/crates/tuono_lib_macros/Cargo.toml +++ b/crates/tuono_lib_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono_lib_macros" -version = "0.12.0" +version = "0.12.1" edition = "2021" description = "The react/rust fullstack framework" keywords = [ "react", "typescript", "fullstack", "web", "ssr"] diff --git a/packages/fs-router-vite-plugin/package.json b/packages/fs-router-vite-plugin/package.json index d15e5414..dd92104a 100644 --- a/packages/fs-router-vite-plugin/package.json +++ b/packages/fs-router-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "tuono-fs-router-vite-plugin", - "version": "0.12.0", + "version": "0.12.1", "description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/lazy-fn-vite-plugin/package.json b/packages/lazy-fn-vite-plugin/package.json index 925f6122..1d952d39 100644 --- a/packages/lazy-fn-vite-plugin/package.json +++ b/packages/lazy-fn-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "tuono-lazy-fn-vite-plugin", - "version": "0.12.0", + "version": "0.12.1", "description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/router/package.json b/packages/router/package.json index 1724aa47..12df99a5 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "tuono-router", - "version": "0.12.0", + "version": "0.12.1", "description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/tuono/package.json b/packages/tuono/package.json index 47bb205f..c2c05bf3 100644 --- a/packages/tuono/package.json +++ b/packages/tuono/package.json @@ -1,6 +1,6 @@ { "name": "tuono", - "version": "0.12.0", + "version": "0.12.1", "description": "The react/rust fullstack framework", "scripts": { "dev": "vite build --watch",