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}