From 60d5bea21c700e0f5ff49081944c8091dec81ec6 Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Wed, 10 Dec 2025 17:19:20 +0000 Subject: [PATCH 1/3] Make logging level configurable Read initial log level from config file if present. Once the server is running the level can be changed via a POST request to /loglevel/{level} where level is one of info, debug or trace. --- src/config.rs | 24 ++++++++++++++++++++++++ src/main.rs | 30 +++++++++++++++++++++++++----- src/model.rs | 3 +-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 706c325..51e1bc6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,8 @@ pub struct GlazedConfig { pub bind_address: SocketAddr, pub public_address: Option, pub tiled_client: TiledClientConfig, + #[serde(default)] + pub log_level: LogLevel, } impl GlazedConfig { pub fn from_file(path: &Path) -> Result { @@ -24,6 +26,7 @@ impl GlazedConfig { tiled_client: TiledClientConfig { address: Url::parse("http://localhost:8000").expect("Static URL is valid"), }, + log_level: LogLevel::Info, } } } @@ -32,3 +35,24 @@ impl GlazedConfig { pub struct TiledClientConfig { pub address: Url, } + +#[derive(Debug, Default, Deserialize, Clone, Copy)] +pub enum LogLevel { + #[default] + #[serde(alias = "info")] + Info, + #[serde(alias = "debug")] + Debug, + #[serde(alias = "trace")] + Trace, +} + +impl From for tracing::level_filters::LevelFilter { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Info => Self::INFO, + LogLevel::Debug => Self::DEBUG, + LogLevel::Trace => Self::TRACE, + } + } +} diff --git a/src/main.rs b/src/main.rs index 914bea3..38761f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use async_graphql::{EmptyMutation, EmptySubscription, Schema}; +use axum::extract::Path; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; use axum::routing::{get, post}; @@ -18,17 +19,24 @@ use serde_json::json; use tokio::select; use tokio::signal::unix::{SignalKind, signal}; use tracing::info; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::layer::SubscriberExt as _; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{Registry, fmt, reload}; use url::Url; use crate::clients::TiledClient; -use crate::config::GlazedConfig; +use crate::config::{GlazedConfig, LogLevel}; use crate::handlers::{download_handler, graphiql_handler, graphql_handler}; use crate::model::TiledQuery; #[tokio::main] async fn main() -> Result<(), Box> { - let subscriber = tracing_subscriber::FmtSubscriber::new(); - tracing::subscriber::set_global_default(subscriber)?; + let (filter, filter_reload) = reload::Layer::new(LevelFilter::INFO); + tracing_subscriber::registry() + .with(filter) + .with(fmt::Layer::default()) + .init(); let cli = Cli::init(); let config; @@ -41,15 +49,21 @@ async fn main() -> Result<(), Box> { info!("Using default config"); config = GlazedConfig::default(); } + filter_reload + .modify(|f| *f = config.log_level.into()) + .unwrap(); match cli.command { - Commands::Serve => serve(config).await, + Commands::Serve => serve(config, filter_reload).await, } } #[derive(Clone)] pub struct RootAddress(Url); -async fn serve(config: GlazedConfig) -> Result<(), Box> { +async fn serve( + config: GlazedConfig, + reload: reload::Handle, +) -> Result<(), Box> { let client = TiledClient::new(config.tiled_client.address); let public_address = config .public_address @@ -72,6 +86,12 @@ async fn serve(config: GlazedConfig) -> Result<(), Box> { get(Json(json!({"version": env!("CARGO_PKG_VERSION")}))), ) .route("/asset/{run}/{stream}/{det}/{id}", get(download_handler)) + .route( + "/loglevel/{level}", + post(|level: Path| async move { + reload.clone().modify(|f| *f = level.0.into()).unwrap(); + }), + ) .with_state(client) .fallback((StatusCode::NOT_FOUND, not_found_page(&public_address))) .layer(Extension(schema)); diff --git a/src/model.rs b/src/model.rs index bd301df..b1cb3a9 100644 --- a/src/model.rs +++ b/src/model.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use async_graphql::{Context, Object, Result, Union}; use serde_json::Value; -use tracing::{info, instrument}; +use tracing::instrument; use crate::RootAddress; use crate::clients::{ClientError, TiledClient}; @@ -175,7 +175,6 @@ impl TableData { .map(|s| s.as_str()) .collect::>() .join("/"); - info!("path: {:?}", p); let table_data = client.table_full(&p, columns, headers).await?; Ok(table_data) From 5b54984399b287ed1143e56ada68186d73317f1f Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Tue, 4 Aug 2026 17:11:59 +0100 Subject: [PATCH 2/3] Remove the log level endpoint level is still configurable via config --- src/main.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 38761f4..892cc27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use async_graphql::{EmptyMutation, EmptySubscription, Schema}; -use axum::extract::Path; use axum::http::StatusCode; use axum::response::{Html, IntoResponse}; use axum::routing::{get, post}; @@ -20,24 +19,18 @@ use tokio::select; use tokio::signal::unix::{SignalKind, signal}; use tracing::info; use tracing::level_filters::LevelFilter; +use tracing_subscriber::fmt; use tracing_subscriber::layer::SubscriberExt as _; use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{Registry, fmt, reload}; use url::Url; use crate::clients::TiledClient; -use crate::config::{GlazedConfig, LogLevel}; +use crate::config::GlazedConfig; use crate::handlers::{download_handler, graphiql_handler, graphql_handler}; use crate::model::TiledQuery; #[tokio::main] async fn main() -> Result<(), Box> { - let (filter, filter_reload) = reload::Layer::new(LevelFilter::INFO); - tracing_subscriber::registry() - .with(filter) - .with(fmt::Layer::default()) - .init(); - let cli = Cli::init(); let config; @@ -49,21 +42,20 @@ async fn main() -> Result<(), Box> { info!("Using default config"); config = GlazedConfig::default(); } - filter_reload - .modify(|f| *f = config.log_level.into()) - .unwrap(); + tracing_subscriber::registry() + .with(LevelFilter::from(config.log_level)) + .with(fmt::Layer::default()) + .init(); + match cli.command { - Commands::Serve => serve(config, filter_reload).await, + Commands::Serve => serve(config).await, } } #[derive(Clone)] pub struct RootAddress(Url); -async fn serve( - config: GlazedConfig, - reload: reload::Handle, -) -> Result<(), Box> { +async fn serve(config: GlazedConfig) -> Result<(), Box> { let client = TiledClient::new(config.tiled_client.address); let public_address = config .public_address @@ -86,12 +78,6 @@ async fn serve( get(Json(json!({"version": env!("CARGO_PKG_VERSION")}))), ) .route("/asset/{run}/{stream}/{det}/{id}", get(download_handler)) - .route( - "/loglevel/{level}", - post(|level: Path| async move { - reload.clone().modify(|f| *f = level.0.into()).unwrap(); - }), - ) .with_state(client) .fallback((StatusCode::NOT_FOUND, not_found_page(&public_address))) .layer(Extension(schema)); From c4440c184d25f2f7e74206b26e18d0ab9c34cf3b Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Wed, 5 Aug 2026 11:03:48 +0100 Subject: [PATCH 3/3] Test level conversion Pretty pointless but up the coverage --- src/config.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/config.rs b/src/config.rs index 51e1bc6..8080cb1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,3 +56,17 @@ impl From for tracing::level_filters::LevelFilter { } } } + +#[cfg(test)] +mod tests { + use tracing::level_filters::LevelFilter; + + use crate::config::LogLevel; + + #[test] + fn level_conversion() { + assert_eq!(LevelFilter::from(LogLevel::Info), LevelFilter::INFO); + assert_eq!(LevelFilter::from(LogLevel::Debug), LevelFilter::DEBUG); + assert_eq!(LevelFilter::from(LogLevel::Trace), LevelFilter::TRACE); + } +}