diff --git a/Cargo.lock b/Cargo.lock index 6b0225f947..298ca1a138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6252,7 +6252,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "2.1.0" +version = "2.2.0" dependencies = [ "ammonia", "anyhow", @@ -6269,6 +6269,8 @@ dependencies = [ "nym-contracts-common", "nym-crypto", "nym-http-api-client", + "nym-http-api-common", + "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-metrics", "nym-node-requests", diff --git a/common/http-api-common/src/middleware/logging.rs b/common/http-api-common/src/middleware/logging.rs index fd60ca30b1..84c33c8a7d 100644 --- a/common/http-api-common/src/middleware/logging.rs +++ b/common/http-api-common/src/middleware/logging.rs @@ -9,13 +9,35 @@ use axum::response::IntoResponse; use axum_client_ip::InsecureClientIp; use colored::Colorize; use std::time::Instant; -use tracing::info; +use tracing::{debug, info}; + +enum LogLevel { + Debug, + Info, +} + +pub async fn log_request_info( + insecure_client_ip: InsecureClientIp, + request: Request, + next: Next, +) -> impl IntoResponse { + log_request(insecure_client_ip, request, next, LogLevel::Info).await +} + +pub async fn log_request_debug( + insecure_client_ip: InsecureClientIp, + request: Request, + next: Next, +) -> impl IntoResponse { + log_request(insecure_client_ip, request, next, LogLevel::Debug).await +} /// Simple logger for requests -pub async fn logger( +async fn log_request( InsecureClientIp(addr): InsecureClientIp, request: Request, next: Next, + level: LogLevel, ) -> impl IntoResponse { // TODO dz use `OriginalUri` extractor to get full URI even for nested // routers if routes aren't logged correctly in handlers @@ -58,7 +80,14 @@ pub async fn logger( let agent_str = "agent".bold(); - info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"); + match level { + LogLevel::Debug => debug!( + "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}" + ), + LogLevel::Info => info!( + "[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}" + ), + } res } diff --git a/nym-api/src/support/http/router.rs b/nym-api/src/support/http/router.rs index 4c291fbc58..588b0e4134 100644 --- a/nym-api/src/support/http/router.rs +++ b/nym-api/src/support/http/router.rs @@ -17,7 +17,7 @@ use axum::response::Redirect; use axum::routing::get; use axum::Router; use core::net::SocketAddr; -use nym_http_api_common::middleware::logging::logger; +use nym_http_api_common::middleware::logging::log_request_info; use tokio::net::TcpListener; use tokio_util::sync::WaitForCancellationFutureOwned; use tower_http::cors::CorsLayer; @@ -91,7 +91,7 @@ impl RouterBuilder { fn finalize_routes(self) -> Router { self.unfinished_router .layer(setup_cors()) - .layer(axum::middleware::from_fn(logger)) + .layer(axum::middleware::from_fn(log_request_info)) } } diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/router/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/http/router/mod.rs index c7030b470d..243d3494d5 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/router/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/router/mod.rs @@ -31,7 +31,7 @@ pub fn build_router(state: ApiState, auth_token: String) -> Router { .nest(routes::API, api::routes(auth_middleware)) // we don't have to be using middleware, but we already had that code // we might want something like: https://github.com/tokio-rs/axum/blob/main/examples/tracing-aka-logging/src/main.rs#L44 instead - .layer(axum::middleware::from_fn(logging::logger)) + .layer(axum::middleware::from_fn(logging::log_request_info)) .with_state(state); cfg_if::cfg_if! { diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index 5e01d55df7..741b5895ec 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -20,7 +20,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["models"]} nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process", "fs"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 936f86cf0a..6ec2945e8f 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "2.1.0" +version = "2.2.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -25,10 +25,12 @@ futures-util = { workspace = true } itertools = { workspace = true } moka = { workspace = true, features = ["future"] } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } nym-bin-common = { path = "../../common/bin-common", features = ["models"] } nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-http-api-client = { path = "../../common/http-api-client" } +nym-http-api-common = { path = "../../common/http-api-common" } nym-network-defaults = { path = "../../common/network-defaults" } nym-serde-helpers = { path = "../../common/serde-helpers" } nym-statistics-common = { path = "../../common/statistics" } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs index 92e9d7d849..b7c330da53 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs @@ -6,6 +6,7 @@ use crate::{ DbPool, }, http::models::Gateway, + mixnet_scraper::helpers::NodeDescriptionResponse, }; use futures_util::TryStreamExt; use sqlx::{pool::PoolConnection, Sqlite}; @@ -131,3 +132,39 @@ pub(crate) async fn get_bonded_gateway_id_keys(pool: &DbPool) -> anyhow::Result< Ok(items) } + +pub(crate) async fn insert_gateway_description( + conn: &mut PoolConnection, + identity_key: &str, + description: &NodeDescriptionResponse, + timestamp: i64, +) -> anyhow::Result<()> { + sqlx::query!( + r#" + INSERT INTO gateway_description ( + gateway_identity_key, + moniker, + website, + security_contact, + details, + last_updated_utc + ) VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (gateway_identity_key) DO UPDATE SET + moniker = excluded.moniker, + website = excluded.website, + security_contact = excluded.security_contact, + details = excluded.details, + last_updated_utc = excluded.last_updated_utc + "#, + identity_key, + description.moniker, + description.website, + description.security_contact, + description.details, + timestamp, + ) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(From::from) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs index 0b5f6e8abd..98709e099e 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use futures_util::TryStreamExt; +use sqlx::{pool::PoolConnection, Sqlite}; use tracing::error; use crate::{ @@ -9,6 +10,7 @@ use crate::{ DbPool, }, http::models::{DailyStats, Mixnode}, + mixnet_scraper::helpers::NodeDescriptionResponse, }; pub(crate) async fn update_mixnodes( @@ -157,3 +159,34 @@ pub(crate) async fn get_bonded_mix_ids(pool: &DbPool) -> anyhow::Result, + mix_id: &i64, + description: &NodeDescriptionResponse, + timestamp: i64, +) -> anyhow::Result<()> { + sqlx::query!( + r#" + INSERT INTO mixnode_description ( + mix_id, moniker, website, security_contact, details, last_updated_utc + ) VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (mix_id) DO UPDATE SET + moniker = excluded.moniker, + website = excluded.website, + security_contact = excluded.security_contact, + details = excluded.details, + last_updated_utc = excluded.last_updated_utc + "#, + mix_id, + description.moniker, + description.website, + description.security_contact, + description.details, + timestamp, + ) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(From::from) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs index d7d85c28f9..59a7984203 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs @@ -4,12 +4,16 @@ use nym_validator_client::{ client::{NodeId, NymNodeDetails}, models::NymNodeDescription, }; +use sqlx::{pool::PoolConnection, Sqlite}; use std::collections::HashMap; use tracing::instrument; -use crate::db::{ - models::{NymNodeDto, NymNodeInsertRecord}, - DbPool, +use crate::{ + db::{ + models::{NymNodeDto, NymNodeInsertRecord}, + DbPool, + }, + mixnet_scraper::helpers::NodeDescriptionResponse, }; pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result> { @@ -194,6 +198,8 @@ pub(crate) async fn get_node_self_description( nym_nodes WHERE self_described IS NOT NULL + ORDER BY + node_id "#, ) .fetch_all(&mut *conn) @@ -256,3 +262,34 @@ pub(crate) async fn get_bonded_node_description( }) .map_err(From::from) } + +pub(crate) async fn insert_nym_node_description( + conn: &mut PoolConnection, + node_id: &i64, + description: &NodeDescriptionResponse, + timestamp: i64, +) -> anyhow::Result<()> { + sqlx::query!( + r#" + INSERT INTO nym_node_descriptions ( + node_id, moniker, website, security_contact, details, last_updated_utc + ) VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (node_id) DO UPDATE SET + moniker = excluded.moniker, + website = excluded.website, + security_contact = excluded.security_contact, + details = excluded.details, + last_updated_utc = excluded.last_updated_utc + "#, + node_id, + description.moniker, + description.website, + description.security_contact, + description.details, + timestamp, + ) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(From::from) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs index aff700595d..6cbc931ead 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs @@ -1,7 +1,11 @@ use crate::{ db::{ models::{ScrapeNodeKind, ScraperNodeInfo}, - queries, DbPool, + queries::{ + self, gateways::insert_gateway_description, mixnodes::insert_mixnode_description, + nym_nodes::insert_nym_node_description, + }, + DbPool, }, mixnet_scraper::helpers::NodeDescriptionResponse, }; @@ -125,78 +129,18 @@ pub(crate) async fn insert_scraped_node_description( match node_kind { ScrapeNodeKind::LegacyMixnode { mix_id } => { - sqlx::query!( - r#" - INSERT INTO mixnode_description ( - mix_id, moniker, website, security_contact, details, last_updated_utc - ) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT (mix_id) DO UPDATE SET - moniker = excluded.moniker, - website = excluded.website, - security_contact = excluded.security_contact, - details = excluded.details, - last_updated_utc = excluded.last_updated_utc - "#, - mix_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, - ) - .execute(&mut *conn) - .await?; + insert_mixnode_description(&mut conn, mix_id, description, timestamp).await?; } ScrapeNodeKind::MixingNymNode { node_id } => { - sqlx::query!( - r#" - INSERT INTO nym_node_descriptions ( - node_id, moniker, website, security_contact, details, last_updated_utc - ) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT (node_id) DO UPDATE SET - moniker = excluded.moniker, - website = excluded.website, - security_contact = excluded.security_contact, - details = excluded.details, - last_updated_utc = excluded.last_updated_utc - "#, - node_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, - ) - .execute(&mut *conn) - .await?; + insert_nym_node_description(&mut conn, node_id, description, timestamp).await?; } - ScrapeNodeKind::EntryExitNymNode { identity_key, .. } => { - sqlx::query!( - r#" - INSERT INTO gateway_description ( - gateway_identity_key, - moniker, - website, - security_contact, - details, - last_updated_utc - ) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT (gateway_identity_key) DO UPDATE SET - moniker = excluded.moniker, - website = excluded.website, - security_contact = excluded.security_contact, - details = excluded.details, - last_updated_utc = excluded.last_updated_utc - "#, - identity_key, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, - ) - .execute(&mut *conn) - .await?; + ScrapeNodeKind::EntryExitNymNode { + node_id, + identity_key, + } => { + insert_nym_node_description(&mut conn, node_id, description, timestamp).await?; + // for historic reasons (/gateways API), store this info into gateways table as well + insert_gateway_description(&mut conn, identity_key, description, timestamp).await?; } } diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs index 409c042753..6c229e94b4 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs @@ -77,7 +77,7 @@ async fn get_all_sessions( }; Ok(Json(PagedResult::paginate( - Pagination { size, page }, + Pagination::new(size, page), day_and_node_filtered, ))) } diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs index 1ba2711ec3..f71f48fe02 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs @@ -1,7 +1,8 @@ use anyhow::anyhow; use axum::{response::Redirect, Router}; +use nym_http_api_common::middleware::logging::log_request_debug; use tokio::net::ToSocketAddrs; -use tower_http::{cors::CorsLayer, trace::TraceLayer}; +use tower_http::cors::CorsLayer; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; @@ -39,7 +40,10 @@ impl RouterBuilder { .nest("/summary", summary::routes()) .nest("/metrics", metrics::routes()), ) - .nest("/v3", Router::new().nest("/nym-nodes", nym_nodes::routes())) + .nest( + "/explorer/v3", + Router::new().nest("/nym-nodes", nym_nodes::routes()), + ) .nest( "/internal", Router::new().nest("/testruns", testruns::routes()), @@ -62,7 +66,7 @@ impl RouterBuilder { // CORS layer needs to wrap other API layers .layer(setup_cors()) // logger should be outermost layer - .layer(TraceLayer::new_for_http()) + .layer(axum::middleware::from_fn(log_request_debug)) } } diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs index 796760de41..3cc221b15b 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs @@ -16,12 +16,13 @@ pub(crate) fn routes() -> Router { } #[utoipa::path( - tag = "Nym Nodes", + tag = "Nym Explorer", get, params( Pagination ), - path = "/v3/nym-nodes", + path = "/nym-nodes", + context_path = "/explorer/v3", responses( (status = 200, body = PagedResult) ) diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs index 4a817ab759..56d2f0c1b2 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs @@ -104,7 +104,10 @@ async fn mixnodes( .map(|(s, _)| s) .collect(); - Ok(Json(PagedResult::paginate(Pagination { size, page }, res))) + Ok(Json(PagedResult::paginate( + Pagination::new(size, page), + res, + ))) } struct ServiceFilter { diff --git a/nym-node-status-api/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs index 2fa8373318..758278641a 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/mod.rs @@ -18,7 +18,7 @@ pub struct PagedResult { impl PagedResult { pub fn paginate(pagination: Pagination, res: Vec) -> Self { let total = res.len(); - let (size, mut page) = pagination.intoto_inner_values(); + let (size, mut page) = pagination.into_inner_values(); if page * size > total { page = total / size; @@ -42,14 +42,25 @@ pub(crate) struct Pagination { page: Option, } +const SIZE_DEFAULT: usize = 10; +const SIZE_MAX: usize = 200; +const PAGE_DEFAULT: usize = 0; + +impl Default for Pagination { + fn default() -> Self { + Self { + size: Some(SIZE_DEFAULT), + page: Some(PAGE_DEFAULT), + } + } +} + impl Pagination { - // unwrap stored values or use predefined defaults - pub(crate) fn intoto_inner_values(self) -> (usize, usize) { - const SIZE_DEFAULT: usize = 10; - const SIZE_MAX: usize = 200; - - const PAGE_DEFAULT: usize = 0; + pub(crate) fn new(size: Option, page: Option) -> Self { + Self { size, page } + } + pub(crate) fn into_inner_values(self) -> (usize, usize) { ( self.size.unwrap_or(SIZE_DEFAULT).min(SIZE_MAX), self.page.unwrap_or(PAGE_DEFAULT), diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index f39decfa73..be9aac0716 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -55,13 +55,13 @@ pub(crate) struct ExtendedNymNode { #[schema(value_type = String)] pub(crate) total_stake: Decimal, pub(crate) original_pledge: u128, - pub(crate) bonding_address: String, + pub(crate) bonding_address: Option, pub(crate) bonded: bool, - pub(crate) node_type: String, + pub(crate) node_type: nym_validator_client::models::DescribedNodeType, pub(crate) ip_address: String, pub(crate) accepted_tnc: bool, - pub(crate) self_description: serde_json::Value, - pub(crate) rewarding_details: serde_json::Value, + pub(crate) self_description: nym_validator_client::models::NymNodeData, + pub(crate) rewarding_details: Option, pub(crate) description: NodeDescription, pub(crate) geoip: Option, } diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index cfd10e936c..fdc58b385d 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -4,7 +4,7 @@ use cosmwasm_std::Decimal; use moka::{future::Cache, Entry}; use nym_contracts_common::NaiveFloat; use nym_crypto::asymmetric::ed25519::PublicKey; -use nym_validator_client::{models::DescribedNodeType, nym_api::SkimmedNode}; +use nym_validator_client::nym_api::SkimmedNode; use tokio::sync::RwLock; use tracing::instrument; @@ -401,11 +401,7 @@ async fn aggregate_node_info_from_db( .get(&node_id) .map(|node| node.performance.naive_to_f64()) .unwrap_or(0.0); - let node_type = match described_node.contract_node_type { - DescribedNodeType::NymNode => "nym_node".to_string(), - DescribedNodeType::LegacyMixnode => "legacy_mixnode".to_string(), - DescribedNodeType::LegacyGateway => "legacy_gateway".to_string(), - }; + let node_type = described_node.contract_node_type; let ip_address = described_node .description .host_information @@ -417,11 +413,10 @@ async fn aggregate_node_info_from_db( .description .auxiliary_details .accepted_operator_terms_and_conditions; - let description = described_node.description; + let self_described = described_node.description; - let bonding_address = bond_details - .map(|details| details.bond_information.owner.to_string()) - .unwrap_or_default(); + let bonding_address = + bond_details.map(|details| details.bond_information.owner.to_string()); let node_description = node_descriptions.get(&node_id).cloned().unwrap_or_default(); let geoip = { @@ -449,8 +444,8 @@ async fn aggregate_node_info_from_db( bonded, node_type, accepted_tnc, - self_description: serde_json::to_value(description).unwrap_or_default(), - rewarding_details: serde_json::to_value(rewarding_details).unwrap_or_default(), + self_description: self_described, + rewarding_details: rewarding_details.to_owned(), description: node_description, geoip, }); diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 56563be519..af70d01170 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -151,7 +151,7 @@ impl Monitor { })?; // refresh geodata for all nodes - for (_, node_description) in described_nodes.iter() { + for node_description in described_nodes.values() { self.location_cached(node_description).await; } @@ -295,7 +295,7 @@ impl Monitor { Ok(()) } - #[instrument(level = "debug", skip_all)] + #[instrument(level = "info", skip_all)] async fn location_cached(&mut self, node: &NymNodeDescription) -> Location { let node_id = node.node_id; diff --git a/nym-node/src/node/http/router/mod.rs b/nym-node/src/node/http/router/mod.rs index 9750b1c0c1..ae9af2371b 100644 --- a/nym-node/src/node/http/router/mod.rs +++ b/nym-node/src/node/http/router/mod.rs @@ -159,7 +159,7 @@ impl NymNodeRouter { ) .nest(routes::LANDING_PAGE, landing_page::routes(config.landing)) .nest(routes::API, api::routes(config.api)) - .layer(axum::middleware::from_fn(logging::logger)) + .layer(axum::middleware::from_fn(logging::log_request_info)) .with_state(state), } }