Create logger service (#101)

* feat: create logger service

* feat: opt out __tuono/data routes from logger

* feat: update version to v0.12.1
This commit is contained in:
Valerio Ageno
2024-11-16 09:47:59 +01:00
committed by GitHub
parent f22249f3e9
commit c394898fcd
13 changed files with 133 additions and 43 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "The react/rust fullstack framework"
+5 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono_lib"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
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"
-15
View File
@@ -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<HashMap<String, String>>,
request: Request,
) -> Html<String> {
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()),
+1
View File
@@ -5,6 +5,7 @@ mod payload;
mod request;
mod response;
mod server;
mod services;
mod ssr;
mod vite_reverse_proxy;
mod vite_websocket_proxy;
+2 -16
View File
@@ -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<dyn Serialize>,
@@ -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(),
+12 -4
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
pub mod logger;
+106
View File
@@ -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<S> Layer<S> for LoggerLayer {
type Service = Logger<S>;
fn layer(&self, inner: S) -> Self::Service {
Logger::new(inner)
}
}
#[derive(Clone)]
pub struct Logger<S> {
inner: S,
}
impl<S> Logger<S> {
pub fn new(inner: S) -> Self {
Logger { inner }
}
}
impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for Logger<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
ResBody: Default,
<S as Service<Request<ReqBody>>>::Error: Debug,
{
type Response = S::Response;
type Error = S::Error;
type Future = LoggerFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> 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<F> {
#[pin]
future: F,
method: Method,
path: String,
start: Instant,
}
impl<F, B, E> Future for LoggerFuture<F>
where
F: Future<Output = Result<Response<B>, E>>,
B: Default,
E: Debug,
{
type Output = Result<Response<B>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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)
}
}
+1 -1
View File
@@ -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"]
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",