1use crate::fmt::Formatter;
2use chrono::prelude::*;
3use chrono::Duration;
4use std::fmt::Debug;
5use std::io::prelude::*;
6use std::thread;
7
8#[derive(Debug, PartialEq, Clone)]
9pub struct LogLocation {
10 pub file_path: String,
11 pub func_path: String,
12 pub lineno: u32,
13 pub thread_id: thread::ThreadId,
14}
15
16#[derive(Debug)]
17pub struct Logger<W: Write> {
18 formatter: Formatter,
19 pub header_interval: Duration,
20 sink: W,
21 prev: Option<(DateTime<Utc>, LogLocation)>,
22}
23
24impl<W: Write> Logger<W> {
25 pub fn new(sink: W) -> Logger<W> {
26 Logger {
27 formatter: Formatter {},
28 header_interval: Duration::seconds(2),
29 sink,
30 prev: None,
31 }
32 }
33
34 fn header(&self, now: DateTime<Utc>, loc: &LogLocation) -> Option<String> {
48 let include_header = self.prev.as_ref().map_or(true, |(last_time, last_loc)| {
50 let elapsed = now.signed_duration_since(*last_time);
51 elapsed >= self.header_interval
52 || loc.file_path != last_loc.file_path
53 || loc.func_path != last_loc.func_path
54 });
55 if include_header {
56 Some(self.formatter.header(&now, loc))
57 } else {
58 None
59 }
60 }
61
62 pub fn q(&mut self, loc: LogLocation) {
63 let log_line = self.formatter.q();
64
65 self.write_log_line(loc, log_line);
66 }
67
68 pub fn q_literal<T: Debug>(&mut self, val: &T, loc: LogLocation) {
69 let log_line = self.formatter.q_literal(val);
70
71 self.write_log_line(loc, log_line);
72 }
73
74 pub fn q_expr<T: Debug>(&mut self, val: &T, expr: &str, loc: LogLocation) {
75 let log_line = self.formatter.q_expr(val, expr);
76
77 self.write_log_line(loc, log_line);
78 }
79
80 fn write_log_line<S: AsRef<str>>(&mut self, loc: LogLocation, log_line: S) {
83 let log_line = log_line.as_ref();
84 let now = Utc::now();
85
86 match self.header(now, &loc) {
87 Some(header) => writeln!(self.sink, "{}\n{}", header, log_line),
88 None => writeln!(self.sink, "{}", log_line),
89 }
90 .expect("Unable to write to log");
91
92 self.prev = Some((now, loc));
93 }
94}
95
96#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn testing_logger() -> Logger<Vec<u8>> {
103 Logger::new(vec![])
104 }
105
106 macro_rules! ll {
107 ($filep: expr, $funcp:expr, $lineno: expr) => {
108 LogLocation {
109 file_path: String::from($filep),
110 func_path: String::from($funcp),
111 lineno: $lineno,
112 thread_id: thread::current().id(),
113 }
114 };
115 }
116
117 #[test]
118 fn test_header_returns_some_if_not_logged_previously() {
119 let logger = testing_logger();
120 let now = Utc.ymd(2020, 6, 22).and_hms(20, 5, 32);
121 let loc = ll!("src/lib.rs", "crate::foo::bar", 42);
122
123 assert_eq!(logger.prev, None);
125
126 assert_eq!(
127 logger.header(now, &loc),
128 Some(format!(
129 "[20:05:32 {:?} src/lib.rs crate::foo::bar:42]",
130 loc.thread_id
131 ))
132 );
133 }
134
135 #[test]
136 fn test_header_returns_some_if_prev_function_differs() {
137 let now = Utc.ymd(2020, 6, 22).and_hms(20, 5, 32);
138 let prev_loc = ll!("src/lib.rs", "crate::foo::bar", 42);
139 let logger = {
140 let mut logger = testing_logger();
141 logger.prev = Some((now, prev_loc));
142 logger
143 };
144
145 let loc = ll!("src/lib.rs", "crate::foo::baz", 50);
146 assert_eq!(
147 logger.header(now, &loc),
148 Some(format!(
149 "[20:05:32 {:?} src/lib.rs crate::foo::baz:50]",
150 loc.thread_id
151 ))
152 );
153 }
154
155 #[test]
156 fn test_header_returns_some_if_prev_module_differs() {
157 let now = Utc.ymd(2020, 6, 22).and_hms(20, 5, 32);
158 let prev_loc = ll!("src/lib.rs", "crate::foo::bar", 42);
159 let logger = {
160 let mut logger = testing_logger();
161 logger.prev = Some((now, prev_loc));
162 logger
163 };
164 let loc = ll!("src/lib.rs", "crate::baz::bat", 42);
165
166 assert_eq!(
167 logger.header(now, &loc),
168 Some(format!(
169 "[20:05:32 {:?} src/lib.rs crate::baz::bat:42]",
170 loc.thread_id
171 ))
172 );
173 }
174
175 #[test]
176 fn test_header_returns_some_if_header_interval_elapsed() {
177 let prev_time = Utc.ymd(2020, 6, 22).and_hms(20, 5, 32);
178 let header_interval = Duration::seconds(2);
179 let loc = ll!("src/lib.rs", "crate::foo::bar", 42);
180 let logger = {
181 let mut logger = testing_logger();
182 logger.prev = Some((prev_time, loc.clone()));
183 logger.header_interval = header_interval;
184 logger
185 };
186
187 let now = Utc.ymd(2020, 6, 22).and_hms(20, 5, 35);
190
191 assert_eq!(
192 logger.header(now, &loc),
193 Some(format!(
194 "[20:05:35 {:?} src/lib.rs crate::foo::bar:42]",
195 loc.thread_id
196 ))
197 );
198 }
199
200 #[test]
201 fn test_header_returns_none_if_header_interval_not_elapsed_and_prev_module_and_function_same() {
202 let prev_time = Utc.ymd(2020, 6, 22).and_hms(20, 5, 32);
203 let header_interval = Duration::seconds(2);
204 let loc = ll!("src/lib.rs", "crate::foo::bar", 42);
205 let logger = {
206 let mut logger = testing_logger();
207 logger.prev = Some((prev_time, loc.clone()));
208 logger.header_interval = header_interval;
209 logger
210 };
211
212 let now = Utc.ymd(2020, 6, 22).and_hms(20, 5, 33);
215
216 assert_eq!(
217 logger.header(now, &loc), None,
219 );
220 }
221}