log_ndc/
lib.rs

1extern crate log;
2
3use log::Log;
4use log::Metadata;
5use log::Record;
6use std::cell::RefCell;
7use std::mem;
8use std::rc::Rc;
9use std::sync::Arc;
10
11pub struct Logger {
12    underlying: Box<dyn Log>,
13}
14
15impl Logger {
16    pub fn new(underlying: Box<dyn Log>) -> Logger {
17        Logger { underlying }
18    }
19}
20
21impl Log for Logger {
22    fn enabled(&self, metadata: &Metadata) -> bool {
23        self.underlying.enabled(metadata)
24    }
25
26    fn log(&self, record: &Record) {
27        get(|ndc| {
28            if ndc.is_empty() {
29                self.underlying.log(record)
30            } else {
31                self.underlying.log(
32                    &Record::builder()
33                        .metadata(record.metadata().clone())
34                        .args(format_args!("[{}] {}", ndc, record.args()))
35                        .module_path(record.module_path())
36                        .file(record.file())
37                        .line(record.line())
38                        .build(),
39                );
40            }
41        })
42    }
43
44    fn flush(&self) {
45        self.underlying.flush()
46    }
47}
48
49pub fn set_boxed_logger(logger: Box<dyn Log>) -> Result<(), log::SetLoggerError> {
50    log::set_boxed_logger(Box::new(Logger::new(logger)))
51}
52
53/// Diagnostic context
54#[derive(Debug, Clone)]
55pub enum Ndc {
56    Str(&'static str),
57    String(String),
58    Rc(Rc<String>),
59    Arc(Arc<String>),
60}
61
62impl Default for Ndc {
63    fn default() -> Self {
64        Ndc::Str("")
65    }
66}
67
68impl Ndc {
69    /// View as string
70    fn to_str(&self) -> &str {
71        match self {
72            Ndc::Str(s) => s,
73            Ndc::String(s) => &s,
74            Ndc::Rc(s) => &**s,
75            Ndc::Arc(s) => &**s,
76        }
77    }
78}
79
80impl From<&'static str> for Ndc {
81    fn from(s: &'static str) -> Self {
82        Ndc::Str(s)
83    }
84}
85
86impl From<String> for Ndc {
87    fn from(s: String) -> Self {
88        Ndc::String(s)
89    }
90}
91
92impl From<Rc<String>> for Ndc {
93    fn from(s: Rc<String>) -> Self {
94        Ndc::Rc(s)
95    }
96}
97
98impl From<Arc<String>> for Ndc {
99    fn from(s: Arc<String>) -> Self {
100        Ndc::Arc(s)
101    }
102}
103
104thread_local! {
105    static NDC: RefCell<Ndc> = RefCell::new(Ndc::Str(""));
106}
107
108#[must_use]
109pub struct SetGuard {
110    ndc: Ndc,
111}
112
113impl Drop for SetGuard {
114    fn drop(&mut self) {
115        set(mem::replace(&mut self.ndc, Default::default()));
116    }
117}
118
119/// Set thread-local context to a given string.
120pub fn set<S: Into<Ndc>>(ndc: S) {
121    replace(ndc);
122}
123
124/// Set thread-local context to a given string and return previously stored value.
125pub fn replace<S: Into<Ndc>>(ndc: S) -> Ndc {
126    NDC.with(|r| mem::replace(&mut *r.borrow_mut(), ndc.into()))
127}
128
129/// Clear diagnostic context
130pub fn clear() {
131    set("");
132}
133
134/// Set thread-local context and return a guard which sets previous value on drop.
135pub fn push<S: Into<Ndc>>(ndc: S) -> SetGuard {
136    SetGuard { ndc: replace(ndc) }
137}
138
139/// Get a copy of the raw NDC object.
140/// Return NDC with empty string if unset.
141pub fn get_raw() -> Ndc {
142    NDC.with(|r| r.borrow().clone())
143}
144
145/// Get a copy of the string in thread-local context.
146/// Empty string is returned when there's no context
147pub fn get_copy() -> String {
148    get(|s| s.to_owned())
149}
150
151/// Read a thread-local context with provided callback.
152pub fn get<R, F>(f: F) -> R
153where
154    F: FnOnce(&str) -> R,
155{
156    NDC.with(|r| f(r.borrow().to_str()))
157}
158
159#[cfg(test)]
160mod test {
161    mod log_ndc {
162        pub use super::super::*;
163    }
164
165    #[test]
166    fn set_get() {
167        let _g = log_ndc::push("");
168
169        log_ndc::set("aa");
170        assert_eq!("aa", log_ndc::get_copy());
171    }
172
173    #[test]
174    fn push() {
175        let _g = log_ndc::push("");
176
177        let _g1 = log_ndc::push("aa");
178        assert_eq!("aa", log_ndc::get_copy());
179
180        let g2 = log_ndc::push("bb");
181        assert_eq!("bb", log_ndc::get_copy());
182        drop(g2);
183
184        assert_eq!("aa", log_ndc::get_copy());
185    }
186}