1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
/*
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 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<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 fn get_log_buffer(warn: bool) -> VecDeque<Arc<LogLine>> {
if warn {
LOGGER.log.read().unwrap().1.clone()
} else {
LOGGER.log.read().unwrap().0.clone()
}
}
pub fn get_log_stream(warn: bool) -> broadcast::Receiver<Arc<LogLine>> {
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();
}
}
|