log_mdc/
lib.rs

1//! A mapped diagnostic context (MDC) for use with the `log` crate.
2//!
3//! An MDC is a thread local map of strings used to make relevant information
4//! from a system available in its log messages. Logging crates such as
5//! [log4rs][log4rs] will retrieve values from the MDC for output.
6//!
7//! For example, a web server may process many requests simultaneously on
8//! different threads. Generating an ID for each request and storing it in the
9//! MDC makes it easy to partition log messages on a per-request basis.
10//!
11//! # Examples
12//!
13//! Forwarding the contents of the MDC to a new thread:
14//!
15//! ```
16//! use std::thread;
17//!
18//! let mut mdc = vec![];
19//! log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned())));
20//!
21//! thread::spawn(|| {
22//!     log_mdc::extend(mdc);
23//! });
24//! ```
25//!
26//! [log4rs]: https://crates.io/crates/log4rs
27#![doc(html_root_url="https://sfackler.github.io/rust-log-mdc/doc/v0.1.0")]
28#![warn(missing_docs)]
29
30use std::borrow::Borrow;
31use std::cell::RefCell;
32use std::collections::HashMap;
33use std::hash::Hash;
34
35thread_local!(static MDC: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new()));
36
37/// Inserts a new entry into the MDC, returning the old value.
38pub fn insert<K, V>(key: K, value: V) -> Option<String>
39    where K: Into<String>,
40          V: Into<String>
41{
42    MDC.with(|m| m.borrow_mut().insert(key.into(), value.into()))
43}
44
45/// Inserts a new entry into the MDC in a scoped fashion.
46///
47/// When the returned guard falls out of scope, it will restore the old value corresponding to the
48/// key.
49///
50/// # Examples
51///
52/// ```
53/// let guard = log_mdc::insert_scoped("foo", "a");
54/// log_mdc::get("foo", |v| assert_eq!(Some("a"), v));
55///
56/// drop(guard);
57/// log_mdc::get("foo", |v| assert_eq!(None, v));
58/// ```
59///
60/// ```
61/// log_mdc::insert("foo", "a");
62///
63/// let guard = log_mdc::insert_scoped("foo", "b");
64/// log_mdc::get("foo", |v| assert_eq!(Some("b"), v));
65///
66/// drop(guard);
67/// log_mdc::get("foo", |v| assert_eq!(Some("a"), v));
68/// ```
69pub fn insert_scoped<K, V>(key: K, value: V) -> InsertGuard
70    where K: Into<String>,
71          V: Into<String>
72{
73    let key = key.into();
74    let old_value = insert(&*key, value);
75
76    InsertGuard {
77        key: Some(key),
78        old_value: old_value,
79    }
80}
81
82/// Extends the MDC with new entries.
83pub fn extend<K, V, I>(entries: I)
84    where K: Into<String>,
85          V: Into<String>,
86          I: IntoIterator<Item = (K, V)>
87{
88    MDC.with(|m| m.borrow_mut().extend(entries.into_iter().map(|(k, v)| (k.into(), v.into()))));
89}
90
91/// Extends the MDC with new entries in a scoped fashion.
92///
93/// When the returned guard falls out of scope, it will restore the old values corresponding to the
94/// keys.
95///
96/// # Examples
97///
98/// ```
99/// log_mdc::insert("foo", "a");
100///
101/// let entries = [
102///     ("foo", "b"),
103///     ("fizz", "buzz"),
104/// ];
105///
106/// let guard = log_mdc::extend_scoped(entries.iter().cloned());
107/// log_mdc::get("foo", |v| assert_eq!(Some("b"), v));
108/// log_mdc::get("fizz", |v| assert_eq!(Some("buzz"), v));
109///
110/// drop(guard);
111/// log_mdc::get("foo", |v| assert_eq!(Some("a"), v));
112/// log_mdc::get("fizz", |v| assert_eq!(None, v));
113/// ```
114pub fn extend_scoped<K, V, I>(entries: I) -> ExtendGuard
115    where K: Into<String>,
116          V: Into<String>,
117          I: IntoIterator<Item = (K, V)>
118{
119    MDC.with(|m| {
120        let mut m = m.borrow_mut();
121
122        let old_entries = entries.into_iter()
123            .map(|(k, v)| (k.into(), v.into()))
124            .map(|(k, v)| {
125                let v = m.insert(k.clone(), v);
126                (k, v)
127            })
128            .collect();
129
130        ExtendGuard(old_entries)
131    })
132}
133
134/// Retrieves a value from the MDC.
135pub fn get<Q: ?Sized, F, T>(key: &Q, f: F) -> T
136    where String: Borrow<Q>,
137          Q: Hash + Eq,
138          F: FnOnce(Option<&str>) -> T
139{
140    MDC.with(|m| f(m.borrow().get(key).map(|v| &**v)))
141}
142
143/// Removes a value from the MDC.
144pub fn remove<Q: ?Sized>(key: &Q) -> Option<String>
145    where String: Borrow<Q>,
146          Q: Hash + Eq
147{
148    MDC.with(|m| m.borrow_mut().remove(key))
149}
150
151/// Removes all values from the MDC.
152pub fn clear() {
153    MDC.with(|m| m.borrow_mut().clear())
154}
155
156/// Invokes the provided closure for each entry in the MDC.
157pub fn iter<F>(mut f: F)
158    where F: FnMut(&str, &str)
159{
160    MDC.with(|m| {
161        for (key, value) in m.borrow().iter() {
162            f(key, value)
163        }
164    })
165}
166
167/// A guard object which restores an MDC entry when dropped.
168pub struct InsertGuard {
169    key: Option<String>,
170    old_value: Option<String>,
171}
172
173impl Drop for InsertGuard {
174    fn drop(&mut self) {
175        let key = self.key.take().unwrap();
176        match self.old_value.take() {
177            Some(value) => insert(key, value),
178            None => remove(&key),
179        };
180    }
181}
182
183/// A guard objects which restores MDC entries when dropped.
184pub struct ExtendGuard(Vec<(String, Option<String>)>);
185
186impl Drop for ExtendGuard {
187    fn drop(&mut self) {
188        MDC.with(|m| {
189            let mut m = m.borrow_mut();
190
191            for (key, value) in self.0.drain(..) {
192                match value {
193                    Some(value) => m.insert(key, value),
194                    None => m.remove(&key),
195                };
196            }
197        })
198    }
199}