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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details.

use lazy_static::lazy_static;
use ringbuf::{Consumer, Producer, RingBuffer};
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

lazy_static! {
    static ref LOG_RECV: Arc<Mutex<LogReceiver>> = Arc::new(Mutex::new(LogReceiver::new()));
}

thread_local! {
    pub static LOG: RefCell<Option<Log>> = RefCell::new(None);
}

pub fn retrieve_log_messages<F: FnMut(&str, &str)>(f: F) {
    if let Ok(mut lr) = LOG_RECV.lock() {
        lr.retrieve_messages(f);
    }
}

#[inline]
pub fn init_thread_logger(name: &'static str) -> bool {
    if !LOG.with(|l| l.borrow().is_some()) {
        if let Ok(mut lr) = LOG_RECV.lock() {
            lr.spawn_global_logger(name);
            return true;
        }
    }

    false
}

pub fn log<F: Fn(&mut std::io::BufWriter<&mut [u8]>)>(f: F) {
    use std::borrow::BorrowMut;

    LOG.with(|l| {
        let mut lh = l.borrow_mut();
        if let Some(lh) = (*(*lh.borrow_mut())).as_mut() {
            lh.log(f);
        }
    });
}

const MAX_LOG_BUFFER: usize = 4096;

pub struct LogReceiver {
    consumers: Vec<(&'static str, Consumer<u8>)>,
}

impl LogReceiver {
    pub fn new() -> Self {
        Self { consumers: vec![] }
    }

    pub fn retrieve_messages<F: FnMut(&str, &str)>(&mut self, mut f: F) {
        for (name, consumer) in self.consumers.iter_mut() {
            let mut buf = [0; 1024];
            let mut oi = 0;

            while let Some(byte) = consumer.pop() {
                if oi >= buf.len() || byte == 0xFF {
                    f(name, std::str::from_utf8(&buf[0..oi]).unwrap());
                    oi = 0;
                } else {
                    buf[oi] = byte;
                    oi += 1;
                }
            }
        }
    }

    pub fn spawn_logger(&mut self, name: &'static str) -> Log {
        let rb = RingBuffer::new(MAX_LOG_BUFFER);
        let (producer, con) = rb.split();

        self.consumers.push((name, con));
        Log { producer, buf: [0; 512] }
    }

    #[inline]
    pub fn spawn_global_logger(&mut self, name: &'static str) {
        let hdl = self.spawn_logger(name);
        LOG.with(move |f| {
            *f.borrow_mut() = Some(hdl);
        });
    }
}

pub struct Log {
    producer: Producer<u8>,
    buf: [u8; 512],
}

impl Log {
    pub fn log_buf(&mut self, data: &[u8]) {
        self.producer.push_slice(data);
        let _ = self.producer.push(0xFF);
    }

    pub fn log<F: Fn(&mut std::io::BufWriter<&mut [u8]>)>(&mut self, f: F) {
        self.buf.fill(0xFF);

        let len = {
            let mut bw = std::io::BufWriter::new(&mut self.buf[..]);
            f(&mut bw);
            bw.buffer().len()
        };

        if len < (self.buf.len() - 1) {
            // include one 0xFF!
            self.producer.push_slice(&self.buf[0..len + 1]);
        } else {
            self.producer.push_slice(&self.buf[0..len]);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_threaded_logger() {
        std::thread::spawn(|| {
            use std::io::Write;
            assert!(init_thread_logger("tstlog"));
            log(|w| write!(w, "Test Log{}!", 1).unwrap());
            log(|w| write!(w, "Test Log{}!", 2).unwrap());
        });

        let mut msgs = vec![];
        for _ in 0..100 {
            std::thread::sleep(std::time::Duration::from_millis(100));

            retrieve_log_messages(|name, s| msgs.push(name.to_string() + "/" + s));

            if msgs.len() > 1 {
                assert_eq!(msgs[0], "tstlog/Test Log1!");
                assert_eq!(msgs[1], "tstlog/Test Log2!");
                break;
            }
        }
    }
}