mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
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:
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono"
|
name = "tuono"
|
||||||
version = "0.12.0"
|
version = "0.12.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||||
description = "The react/rust fullstack framework"
|
description = "The react/rust fullstack framework"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono_lib"
|
name = "tuono_lib"
|
||||||
version = "0.12.0"
|
version = "0.12.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||||
description = "The react/rust fullstack framework"
|
description = "The react/rust fullstack framework"
|
||||||
@@ -33,9 +33,12 @@ either = "1.13.0"
|
|||||||
tower-http = {version = "0.6.0", features = ["fs"]}
|
tower-http = {version = "0.6.0", features = ["fs"]}
|
||||||
colored = "2.1.0"
|
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
|
# Match the same version used by axum
|
||||||
tokio-tungstenite = "0.24.0"
|
tokio-tungstenite = "0.24.0"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||||
tungstenite = "0.24.0"
|
tungstenite = "0.24.0"
|
||||||
|
http = "1.1.0"
|
||||||
|
pin-project = "1.1.7"
|
||||||
|
tower = "0.5.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use crate::{ssr::Js, Payload};
|
use crate::{ssr::Js, Payload};
|
||||||
use axum::extract::{Path, Request};
|
use axum::extract::{Path, Request};
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use colored::*;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::time::Instant;
|
|
||||||
|
|
||||||
pub async fn catch_all(
|
pub async fn catch_all(
|
||||||
Path(params): Path<HashMap<String, String>>,
|
Path(params): Path<HashMap<String, String>>,
|
||||||
request: Request,
|
request: Request,
|
||||||
) -> Html<String> {
|
) -> Html<String> {
|
||||||
let start = Instant::now();
|
|
||||||
let pathname = request.uri();
|
let pathname = request.uri();
|
||||||
let headers = request.headers();
|
let headers = request.headers();
|
||||||
|
|
||||||
@@ -20,18 +17,6 @@ pub async fn catch_all(
|
|||||||
|
|
||||||
let result = Js::render_to_string(Some(&payload));
|
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 {
|
match result {
|
||||||
Ok(html) => Html(html),
|
Ok(html) => Html(html),
|
||||||
_ => Html("500 internal server error".to_string()),
|
_ => Html("500 internal server error".to_string()),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod payload;
|
|||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
mod server;
|
mod server;
|
||||||
|
mod services;
|
||||||
mod ssr;
|
mod ssr;
|
||||||
mod vite_reverse_proxy;
|
mod vite_reverse_proxy;
|
||||||
mod vite_websocket_proxy;
|
mod vite_websocket_proxy;
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ use crate::{ssr::Js, Payload};
|
|||||||
use axum::http::{HeaderMap, StatusCode};
|
use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{Html, IntoResponse, Redirect};
|
use axum::response::{Html, IntoResponse, Redirect};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use colored::*;
|
|
||||||
use erased_serde::Serialize;
|
use erased_serde::Serialize;
|
||||||
use tokio::time::Instant;
|
|
||||||
|
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
data: Box<dyn Serialize>,
|
data: Box<dyn Serialize>,
|
||||||
@@ -74,25 +72,13 @@ impl Response {
|
|||||||
pub fn render_to_string(&self, req: Request) -> impl IntoResponse {
|
pub fn render_to_string(&self, req: Request) -> impl IntoResponse {
|
||||||
match self {
|
match self {
|
||||||
Self::Props(Props { data, http_code }) => {
|
Self::Props(Props { data, http_code }) => {
|
||||||
let start = Instant::now();
|
|
||||||
let payload = Payload::new(&req, data).client_payload().unwrap();
|
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)),
|
Ok(html) => (*http_code, Html(html)),
|
||||||
Err(_) => (*http_code, Html("500 Internal server error".to_string())),
|
Err(_) => (*http_code, Html("500 Internal server error".to_string())),
|
||||||
}
|
}
|
||||||
.into_response();
|
.into_response()
|
||||||
|
|
||||||
let duration = start.elapsed();
|
|
||||||
|
|
||||||
println!(
|
|
||||||
" GET {} {} in {}ms",
|
|
||||||
req.uri.path(),
|
|
||||||
http_code.as_str().green(),
|
|
||||||
duration.as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
}
|
||||||
Self::Redirect(to) => Redirect::permanent(to).into_response(),
|
Self::Redirect(to) => Redirect::permanent(to).into_response(),
|
||||||
Self::Custom(response) => response.clone().into_response(),
|
Self::Custom(response) => response.clone().into_response(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::mode::{Mode, GLOBAL_MODE};
|
|
||||||
|
|
||||||
use crate::manifest::load_manifest;
|
use crate::manifest::load_manifest;
|
||||||
|
use crate::mode::{Mode, GLOBAL_MODE};
|
||||||
|
use crate::services::logger::LoggerLayer;
|
||||||
use axum::routing::{get, Router};
|
use axum::routing::{get, Router};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use ssr_rs::Ssr;
|
use ssr_rs::Ssr;
|
||||||
@@ -42,9 +42,13 @@ impl Server {
|
|||||||
let router = self
|
let router = self
|
||||||
.router
|
.router
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
.layer(LoggerLayer::new())
|
||||||
.route("/vite-server/", get(vite_websocket_proxy))
|
.route("/vite-server/", get(vite_websocket_proxy))
|
||||||
.route("/vite-server/*path", get(vite_reverse_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);
|
.with_state(fetch);
|
||||||
|
|
||||||
axum::serve(listener, router)
|
axum::serve(listener, router)
|
||||||
@@ -58,7 +62,11 @@ impl Server {
|
|||||||
let router = self
|
let router = self
|
||||||
.router
|
.router
|
||||||
.to_owned()
|
.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);
|
.with_state(fetch);
|
||||||
|
|
||||||
axum::serve(listener, router)
|
axum::serve(listener, router)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod logger;
|
||||||
@@ -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,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono_lib_macros"
|
name = "tuono_lib_macros"
|
||||||
version = "0.12.0"
|
version = "0.12.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "The react/rust fullstack framework"
|
description = "The react/rust fullstack framework"
|
||||||
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
|
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono-fs-router-vite-plugin",
|
"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",
|
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono-lazy-fn-vite-plugin",
|
"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",
|
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono-router",
|
"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",
|
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono",
|
"name": "tuono",
|
||||||
"version": "0.12.0",
|
"version": "0.12.1",
|
||||||
"description": "The react/rust fullstack framework",
|
"description": "The react/rust fullstack framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
|
|||||||
Reference in New Issue
Block a user