aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/admin/log.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/routes/ui/admin/log.rs')
-rw-r--r--server/src/routes/ui/admin/log.rs258
1 files changed, 0 insertions, 258 deletions
diff --git a/server/src/routes/ui/admin/log.rs b/server/src/routes/ui/admin/log.rs
deleted file mode 100644
index fc85b37..0000000
--- a/server/src/routes/ui/admin/log.rs
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2025 metamuffin <metamuffin.org>
-*/
-use crate::{
- routes::ui::{
- account::session::AdminSession,
- error::MyResult,
- layout::{DynLayoutPage, LayoutPage},
- },
- uri,
-};
-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::{Arc, LazyLock, RwLock},
-};
-use tokio::sync::broadcast;
-
-const MAX_LOG_LEN: usize = 4096;
-
-static LOGGER: LazyLock<Log> = LazyLock::new(Log::default);
-
-pub fn enable_logging() {
- log::set_logger(&*LOGGER).unwrap();
- log::set_max_level(log::LevelFilter::Debug);
-}
-
-type LogBuffer = VecDeque<Arc<LogLine>>;
-
-pub struct Log {
- inner: env_logger::Logger,
- stream: (
- broadcast::Sender<Arc<LogLine>>,
- broadcast::Sender<Arc<LogLine>>,
- ),
- log: RwLock<(LogBuffer, LogBuffer)>,
-}
-
-pub struct LogLine {
- time: DateTime<Utc>,
- module: Option<&'static str>,
- level: Level,
- message: String,
-}
-
-#[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[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()] {
- td.time { @e.time.to_rfc3339() }
- td.loglevel { @format_level(e.level) }
- td.module { @e.module }
- td { @markup::raw(vt100_to_html(&e.message)) }
- }
- }}
- }
- },
- })
-}
-
-#[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 {
- inner: env_logger::builder()
- .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(),
- }
- }
-}
-impl Log {
- fn should_log(&self, metadata: &log::Metadata) -> bool {
- let level = metadata.level();
- level
- <= match metadata.target() {
- x if x.starts_with("jelly") => Level::Debug,
- x if x.starts_with("rocket::") => Level::Info,
- _ => Level::Warn,
- }
- }
- fn do_log(&self, record: &log::Record) {
- let time = Utc::now();
- 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 {
- let _ = self.stream.1.send(line.clone());
- w.1.push_back(line);
- while w.1.len() > MAX_LOG_LEN {
- w.1.pop_front();
- }
- }
- }
-}
-
-impl log::Log for Log {
- fn enabled(&self, metadata: &log::Metadata) -> bool {
- self.inner.enabled(metadata) || self.should_log(metadata)
- }
- fn log(&self, record: &log::Record) {
- match (record.module_path_static(), record.line()) {
- // TODO is there a better way to ignore those?
- (Some("rocket::rocket"), Some(670)) => return,
- (Some("rocket::server"), Some(401)) => return,
- _ => {}
- }
- if self.inner.enabled(record.metadata()) {
- self.inner.log(record);
- }
- if self.should_log(record.metadata()) {
- self.do_log(record)
- }
- }
- fn flush(&self) {
- self.inner.flush();
- }
-}
-
-fn vt100_to_html(s: &str) -> String {
- let mut out = HtmlOut::default();
- let mut st = vte::Parser::new();
- st.advance(&mut out, s.as_bytes());
- out.s
-}
-
-fn format_level(level: Level) -> impl markup::Render {
- let (s, c) = match level {
- Level::Debug => ("DEBUG", "blue"),
- Level::Error => ("ERROR", "red"),
- Level::Warn => ("WARN", "yellow"),
- Level::Info => ("INFO", "green"),
- Level::Trace => ("TRACE", "lightblue"),
- };
- 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 {
- s: String,
- color: bool,
-}
-impl HtmlOut {
- pub fn set_color(&mut self, [r, g, b]: [u8; 3]) {
- self.reset_color();
- self.color = true;
- write!(self.s, "<span style=color:#{:02x}{:02x}{:02x}>", r, g, b).unwrap()
- }
- pub fn reset_color(&mut self) {
- if self.color {
- write!(self.s, "</span>").unwrap();
- self.color = false;
- }
- }
-}
-impl vte::Perform for HtmlOut {
- fn print(&mut self, c: char) {
- match c {
- 'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' => self.s.push(c),
- x => write!(self.s, "&#{};", x as u32).unwrap(),
- }
- }
- fn execute(&mut self, _byte: u8) {}
- fn hook(&mut self, _params: &vte::Params, _i: &[u8], _ignore: bool, _a: char) {}
- fn put(&mut self, _byte: u8) {}
- fn unhook(&mut self) {}
- fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
- fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
- fn csi_dispatch(
- &mut self,
- params: &vte::Params,
- _intermediates: &[u8],
- _ignore: bool,
- action: char,
- ) {
- let mut k = params.iter();
- #[allow(clippy::single_match)]
- match action {
- 'm' => match k.next().unwrap_or(&[0]).first().unwrap_or(&0) {
- c @ (30..=37 | 40..=47) => {
- let c = if *c >= 40 { *c - 10 } else { *c };
- self.set_color(match c {
- 30 => [0, 0, 0],
- 31 => [255, 0, 0],
- 32 => [0, 255, 0],
- 33 => [255, 255, 0],
- 34 => [0, 0, 255],
- 35 => [255, 0, 255],
- 36 => [0, 255, 255],
- 37 => [255, 255, 255],
- _ => unreachable!(),
- });
- }
- _ => (),
- },
- _ => (),
- }
- }
-}