From 812ba9dc3bbd990ee2fb1475b36762382b5b1c77 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Mon, 21 Apr 2025 16:52:14 +0200 Subject: [PATCH] Add logging functionality to the dev panel --- backend/scss/main.scss | 6 ++ backend/src/html_templates.rs | 3 + backend/src/log.rs | 115 ++++++++++++++++++++++--------- backend/src/main.rs | 11 ++- backend/src/services/mod.rs | 17 ++++- backend/templates/dev_panel.html | 27 +++++++- 6 files changed, 141 insertions(+), 38 deletions(-) diff --git a/backend/scss/main.scss b/backend/scss/main.scss index 062d3ff..2c65450 100644 --- a/backend/scss/main.scss +++ b/backend/scss/main.scss @@ -187,6 +187,12 @@ body { } } + #dev-panel { + .log-line-odd { + background-color: consts.$color-1; + } + } + form { display: grid; grid-template-columns: auto 1fr; diff --git a/backend/src/html_templates.rs b/backend/src/html_templates.rs index 922d776..d5fd3fd 100644 --- a/backend/src/html_templates.rs +++ b/backend/src/html_templates.rs @@ -3,6 +3,7 @@ use askama::Template; use crate::{ Context, data::{db, model}, + log::Log, translation::{self, Sentence, Tr}, }; @@ -58,6 +59,8 @@ pub struct HomeTemplate { pub struct DevPanelTemplate { pub context: Context, pub recipes: Recipes, + pub log: Log, + pub current_log_file: String, } #[derive(Template)] diff --git a/backend/src/log.rs b/backend/src/log.rs index 45ab1b9..8842aa7 100644 --- a/backend/src/log.rs +++ b/backend/src/log.rs @@ -1,5 +1,12 @@ -use std::{fs, path::Path}; +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + path::{Path, PathBuf}, + sync::Arc, +}; +use itertools::Itertools; +use tracing::{Level, event}; use tracing_appender::{ non_blocking::WorkerGuard, rolling::{RollingFileAppender, Rotation}, @@ -22,38 +29,82 @@ const TRACING_LEVEL: tracing::Level = tracing::Level::INFO; #[cfg(not(debug_assertions))] const TRACING_DISPLAY_THREAD: bool = false; -pub fn init

(directory: P) -> WorkerGuard -where - P: AsRef, -{ - if !directory.as_ref().exists() { - fs::DirBuilder::new().create(&directory).unwrap(); +#[derive(Clone)] +pub struct Log { + guard: Arc, + directory: PathBuf, +} + +// TODO: Remove all 'unwrap'. +impl Log { + pub fn new

(directory: P) -> Self + where + P: AsRef, + { + if !directory.as_ref().exists() { + fs::DirBuilder::new().create(&directory).unwrap(); + } + + let file_appender = RollingFileAppender::builder() + .rotation(Rotation::DAILY) + .filename_prefix(consts::BASE_LOG_FILENAME) + .filename_suffix("log") + .build(&directory) + .expect("Initializing rolling file appender failed"); + + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); + + let layer_file = tracing_subscriber::fmt::layer() + .with_writer(non_blocking.with_max_level(TRACING_LEVEL)) + .with_ansi(false) + .with_thread_ids(TRACING_DISPLAY_THREAD) + .with_thread_names(TRACING_DISPLAY_THREAD); + + let layer_stdout = tracing_subscriber::fmt::layer() + .with_writer(std::io::stdout.with_max_level(TRACING_LEVEL)) + .with_thread_ids(TRACING_DISPLAY_THREAD) + .with_thread_names(TRACING_DISPLAY_THREAD); + + tracing_subscriber::Registry::default() + .with(layer_file) + .with(layer_stdout) + .init(); + + Log { + guard: Arc::new(guard), + directory: directory.as_ref().to_path_buf(), + } } - let file_appender = RollingFileAppender::builder() - .rotation(Rotation::DAILY) - .filename_prefix(consts::BASE_LOG_FILENAME) - .filename_suffix("log") - .build(directory) - .expect("Initializing rolling file appender failed"); + pub fn file_names(&self) -> std::io::Result> { + Ok(self + .directory + .read_dir()? + .map(|entry| { + entry + .unwrap() + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string() + }) + .sorted() + .collect()) + } - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - - let layer_file = tracing_subscriber::fmt::layer() - .with_writer(non_blocking.with_max_level(TRACING_LEVEL)) - .with_ansi(false) - .with_thread_ids(TRACING_DISPLAY_THREAD) - .with_thread_names(TRACING_DISPLAY_THREAD); - - let layer_stdout = tracing_subscriber::fmt::layer() - .with_writer(std::io::stdout.with_max_level(TRACING_LEVEL)) - .with_thread_ids(TRACING_DISPLAY_THREAD) - .with_thread_names(TRACING_DISPLAY_THREAD); - - tracing_subscriber::Registry::default() - .with(layer_file) - .with(layer_stdout) - .init(); - - guard + pub fn read_content(&self, filename: &str) -> std::io::Result> { + let filepath = self.directory.join(filename); + if filepath.is_file() { + let file = File::open(filepath)?; + // tracing::event!(Level::ERROR, "file: {:?}", file); + Ok(BufReader::new(file) + .lines() + .map(|l| l.unwrap_or_default()) + .collect()) + } else { + Ok(vec![]) + } + } } diff --git a/backend/src/main.rs b/backend/src/main.rs index d668b64..6cf9d2e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -26,6 +26,7 @@ use tower_http::{ use tracing::{Level, event}; use data::{backup, db, model}; +use log::Log; use translation::Tr; mod config; @@ -45,6 +46,7 @@ mod utils; struct AppState { config: Config, db_connection: db::Connection, + log: Log, } impl FromRef for Config { @@ -59,6 +61,12 @@ impl FromRef for db::Connection { } } +impl FromRef for Log { + fn from_ref(app_state: &AppState) -> Log { + app_state.log.clone() + } +} + impl axum::response::IntoResponse for db::DBError { fn into_response(self) -> Response { ron_utils::ron_error(StatusCode::INTERNAL_SERVER_ERROR, &self.to_string()).into_response() @@ -103,7 +111,7 @@ impl Context { #[tokio::main] async fn main() { let config = config::load(); - let _guard = log::init(&config.logs_directory); + let log = Log::new(&config.logs_directory); event!(Level::INFO, "Configuration: {:?}", config); @@ -133,6 +141,7 @@ async fn main() { let state = AppState { config, db_connection, + log, }; let ron_api_routes = Router::new() diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index 37122e5..3483809 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -1,13 +1,14 @@ use askama::Template; use axum::{ body, debug_handler, - extract::{Extension, Request, State}, + extract::{Extension, Query, Request, State}, http::{StatusCode, header}, middleware::Next, response::{Html, IntoResponse, Response}, }; +use serde::Deserialize; -use crate::{Context, Result, consts, data::db, html_templates::*, ron_utils}; +use crate::{AppState, Context, Result, consts, data::db, html_templates::*, log::Log, ron_utils}; pub mod fragments; pub mod recipe; @@ -62,10 +63,18 @@ pub async fn home_page( ///// DEV_PANEL ///// -#[debug_handler] +#[derive(Deserialize)] +pub struct LogFile { + #[serde(default)] + pub log_file: String, +} + +#[debug_handler(state = AppState)] pub async fn dev_panel( State(connection): State, + State(log): State, Extension(context): Extension, + log_file: Query, ) -> Result { if context.user.is_some() && context.user.as_ref().unwrap().is_admin { Ok(Html( @@ -73,6 +82,8 @@ pub async fn dev_panel( recipes: Recipes::new(connection, &context.user, context.tr.current_lang_code()) .await?, context, + log, + current_log_file: log_file.log_file.clone(), } .render()?, ) diff --git a/backend/templates/dev_panel.html b/backend/templates/dev_panel.html index 89a90d1..18a5ccf 100644 --- a/backend/templates/dev_panel.html +++ b/backend/templates/dev_panel.html @@ -2,13 +2,36 @@ {% block content %} -

+
+ +
+
    + {% for f in log.file_names().unwrap() %} +
  • {{ f }}
  • + {% endfor %} +
+
+ {% match log.read_content(current_log_file) %} + {% when Ok(lines) %} + {% for l in lines %} +
{{ l }}
+ {% endfor %} + {% when Err(err) %} + Error reading log: {{ err }} + {% endmatch %} +
+
-