diff options
Diffstat (limited to 'server/src/routes/ui/admin/log.rs')
-rw-r--r-- | server/src/routes/ui/admin/log.rs | 66 |
1 files changed, 54 insertions, 12 deletions
diff --git a/server/src/routes/ui/admin/log.rs b/server/src/routes/ui/admin/log.rs index cc34dbc..f962138 100644 --- a/server/src/routes/ui/admin/log.rs +++ b/server/src/routes/ui/admin/log.rs @@ -13,12 +13,16 @@ use crate::{ }; use chrono::{DateTime, Utc}; use log::Level; +use markup::Render; use rocket::get; +use rocket_ws::{Message, Stream, WebSocket}; +use serde_json::json; use std::{ collections::VecDeque, fmt::Write, - sync::{LazyLock, RwLock}, + sync::{Arc, LazyLock, RwLock}, }; +use tokio::sync::broadcast; const MAX_LOG_LEN: usize = 4096; @@ -31,7 +35,11 @@ pub fn enable_logging() { pub struct Log { inner: env_logger::Logger, - log: RwLock<(VecDeque<LogLine>, VecDeque<LogLine>)>, + stream: ( + broadcast::Sender<Arc<LogLine>>, + broadcast::Sender<Arc<LogLine>>, + ), + log: RwLock<(VecDeque<Arc<LogLine>>, VecDeque<Arc<LogLine>>)>, } pub struct LogLine { @@ -41,14 +49,15 @@ pub struct LogLine { message: String, } -#[get("/admin/log?<warnonly>")] +#[get("/admin/log?<warnonly>", rank = 2)] pub fn r_admin_log<'a>(_session: AdminSession, warnonly: bool) -> MyResult<DynLayoutPage<'a>> { Ok(LayoutPage { title: "Log".into(), + class: Some("admin_log"), content: markup::new! { h1 { "Server Log" } a[href=uri!(r_admin_log(!warnonly))] { @if warnonly { "Show everything" } else { "Show only warnings" }} - code.log { + code.log[id="log"] { @let g = LOGGER.log.read().unwrap(); table { @for e in if warnonly { g.1.iter() } else { g.0.iter() } { tr[class=format!("level-{:?}", e.level).to_ascii_lowercase()] { @@ -63,6 +72,32 @@ pub fn r_admin_log<'a>(_session: AdminSession, warnonly: bool) -> MyResult<DynLa ..Default::default() }) } + +#[get("/admin/log?stream&<warnonly>", rank = 1)] +pub fn r_admin_log_stream( + _session: AdminSession, + ws: WebSocket, + warnonly: bool, +) -> Stream!['static] { + let mut stream = if warnonly { + LOGGER.stream.1.subscribe() + } else { + LOGGER.stream.0.subscribe() + }; + Stream! { ws => + let _ = ws; + while let Ok(line) = stream.recv().await { + yield Message::Text(json!({ + "time": line.time, + "level_class": format!("level-{:?}", line.level).to_ascii_lowercase(), + "level_html": format_level_string(line.level), + "module": line.module, + "message": vt100_to_html(&line.message), + }).to_string()); + } + } +} + impl Default for Log { fn default() -> Self { Self { @@ -70,6 +105,10 @@ impl Default for Log { .filter_level(log::LevelFilter::Warn) .parse_env("LOG") .build(), + stream: ( + tokio::sync::broadcast::channel(1024).0, + tokio::sync::broadcast::channel(1024).0, + ), log: Default::default(), } } @@ -85,24 +124,22 @@ impl Log { } } fn do_log(&self, record: &log::Record) { - let mut w = self.log.write().unwrap(); let time = Utc::now(); - w.0.push_back(LogLine { + let line = Arc::new(LogLine { time, module: record.module_path_static(), level: record.level(), message: record.args().to_string(), }); + let mut w = self.log.write().unwrap(); + w.0.push_back(line.clone()); + let _ = self.stream.0.send(line.clone()); while w.0.len() > MAX_LOG_LEN { w.0.pop_front(); } if record.level() <= Level::Warn { - w.1.push_back(LogLine { - time, - module: record.module_path_static(), - level: record.level(), - message: record.args().to_string(), - }); + let _ = self.stream.1.send(line.clone()); + w.1.push_back(line); while w.1.len() > MAX_LOG_LEN { w.1.pop_front(); } @@ -150,6 +187,11 @@ fn format_level(level: Level) -> impl markup::Render { }; markup::new! { span[style=format!("color:{c}")] {@s} } } +fn format_level_string(level: Level) -> String { + let mut w = String::new(); + format_level(level).render(&mut w).unwrap(); + w +} #[derive(Default)] pub struct HtmlOut { |