/* 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 */ use jellycommon::{ api::{LogLevel, LogLine}, chrono::Utc, }; use log::Level; use std::{ collections::VecDeque, sync::{Arc, LazyLock, RwLock}, }; use tokio::sync::broadcast; const MAX_LOG_LEN: usize = 4096; static LOGGER: LazyLock = LazyLock::new(Log::default); pub fn enable_logging() { log::set_logger(&*LOGGER).unwrap(); log::set_max_level(log::LevelFilter::Debug); } type LogBuffer = VecDeque>; pub struct Log { inner: env_logger::Logger, stream: ( broadcast::Sender>, broadcast::Sender>, ), log: RwLock<(LogBuffer, LogBuffer)>, } pub fn get_log_buffer(warn: bool) -> VecDeque> { if warn { LOGGER.log.read().unwrap().1.clone() } else { LOGGER.log.read().unwrap().0.clone() } } pub fn get_log_stream(warn: bool) -> broadcast::Receiver> { if warn { LOGGER.stream.0.subscribe() } else { LOGGER.stream.1.subscribe() } } 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: match record.level() { Level::Error => LogLevel::Error, Level::Warn => LogLevel::Warn, Level::Info => LogLevel::Info, Level::Debug => LogLevel::Debug, Level::Trace => LogLevel::Trace, }, 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(); } }