aboutsummaryrefslogtreecommitdiff
path: root/logic/src/admin/log.rs
blob: 7130c450aaba03055b7fe6b80de6274502700874 (plain)
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.1.subscribe()
    } else {
        LOGGER.stream.0.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();
    }
}