1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3
4use std::{env, fmt};
5
6use log::{Level, SetLoggerError};
7
8#[cfg(any(test, not(all(feature = "pretty_env_logger", debug_assertions))))]
9use serde_json::{json, Value};
10
11#[cfg(feature = "cargo")]
12#[doc(hidden)]
13#[macro_use]
14pub mod macros;
15
16#[cfg(feature = "customfields")]
17use log::kv;
18
19#[cfg(feature = "customfields")]
20use std::collections::HashMap;
21
22struct LogLevel(Level);
24
25#[cfg(feature = "customfields")]
28struct CustomFields<'kvs>(HashMap<kv::Key<'kvs>, kv::Value<'kvs>>);
29
30#[cfg(feature = "customfields")]
31impl<'kvs> CustomFields<'kvs> {
32 fn new() -> Self {
33 Self(HashMap::new())
34 }
35
36 fn inner(&self) -> &HashMap<kv::Key, kv::Value> {
37 &self.0
38 }
39}
40
41#[derive(Clone)]
43pub struct Service {
44 pub name: String,
46
47 pub version: String,
49}
50
51impl Service {
52 pub fn from_env() -> Option<Service> {
53 let name = env::var("SERVICE_NAME")
54 .or_else(|_| env::var("CARGO_PKG_NAME"))
55 .unwrap_or_else(|_| String::new());
56
57 let version = env::var("SERVICE_VERSION")
58 .or_else(|_| env::var("CARGO_PKG_VERSION"))
59 .unwrap_or_else(|_| String::new());
60
61 if name.is_empty() && version.is_empty() {
62 return None;
63 }
64
65 Some(Service { name, version })
66 }
67}
68
69pub fn init() {
79 try_init(None, true).expect("Could not initialize stackdriver_logger");
80}
81
82pub fn init_with(service: Option<Service>, report_location: bool) {
106 try_init(service, report_location).expect("Could not initialize stackdriver_logger");
107}
108
109#[allow(unused_variables)]
112pub(crate) fn try_init(
113 service: Option<Service>,
114 report_location: bool,
115) -> Result<(), SetLoggerError> {
116 #[cfg(all(feature = "pretty_env_logger", debug_assertions))]
117 {
118 #[cfg(feature = "customfields")]
119 {
120 use std::io::Write;
121 let mut builder = env_logger::Builder::new();
122 builder.format(move |f, record| writeln!(f, "{}", format_record_pretty(record)));
123 }
124
125 pretty_env_logger::try_init()
126 }
127
128 #[cfg(not(all(feature = "pretty_env_logger", debug_assertions)))]
129 {
130 use std::io::Write;
131 let mut builder = env_logger::Builder::new();
132 builder.format(move |f, record| {
133 writeln!(
134 f,
135 "{}",
136 format_record(record, service.as_ref(), report_location)
137 )
138 });
139
140 if let Ok(s) = ::std::env::var("RUST_LOG") {
141 builder.parse_filters(&s);
142 }
143
144 builder.try_init()
145 }
146}
147
148impl fmt::Display for LogLevel {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.write_str(match self {
152 LogLevel(Level::Error) => "ERROR",
153 LogLevel(Level::Warn) => "WARNING",
154 LogLevel(Level::Info) => "INFO",
155
156 LogLevel(_) => "DEBUG",
158 })
159 }
160}
161
162#[cfg(feature = "customfields")]
163impl<'kvs> kv::Visitor<'kvs> for CustomFields<'kvs> {
164 fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> {
165 self.0.insert(key, value);
166 Ok(())
167 }
168}
169
170#[cfg(any(test, not(all(feature = "pretty_env_logger", debug_assertions))))]
172fn format_record(
173 record: &log::Record<'_>,
174 service: Option<&Service>,
175 report_location: bool,
176) -> Value {
177 let json_payload = json!({
178 "eventTime": chrono::Utc::now().to_rfc3339(),
179 "severity": LogLevel(record.level()).to_string(),
180
181 "message": match record.level() {
183 Level::Error => format!(
184 "{} \n at {}:{}",
185 record.args(),
186 record.file().unwrap_or("unknown_file"),
187 record.line().unwrap_or(0)
188 ),
189 _ => format!("{}", record.args()),
190 },
191
192 "serviceContext": service.map(|s| json!({
194 "service": s.name,
195 "version": s.version
196 }))
197 .unwrap_or_else(|| json!({
198 "service": "unknown_service"
199 })),
200
201 "reportLocation": if report_location {
203 json!({
204 "filePath": record.file(),
205 "modulePath": record.module_path(),
206 "lineNumber": record.line(),
207 })
208 } else {
209 Value::Null
210 }
211 });
212
213 #[cfg(not(feature = "customfields"))]
214 return json_payload;
215
216 #[cfg(feature = "customfields")]
217 {
218 let mut json_payload = json_payload;
219 let mut custom_fields = CustomFields::new();
220 if record.key_values().visit(&mut custom_fields).is_ok() {
221 for (key, val) in custom_fields.inner().iter() {
222 json_payload[key.as_str()] = Value::String(val.to_string());
223 }
224 }
225 return json_payload;
226 }
227}
228
229#[cfg(all(
230 feature = "pretty_env_logger",
231 feature = "customfields",
232 debug_assertions
233))]
234fn format_record_pretty(record: &log::Record<'_>) -> String {
235 let mut message = format!("{}", record.args());
236 let mut custom_fields = CustomFields::new();
237 let mut kv_message_parts = vec![];
238 if record.key_values().visit(&mut custom_fields).is_ok() {
239 for (key, val) in custom_fields.inner().iter() {
240 kv_message_parts.push(format!("{}={}", key, val));
241 }
242 }
243
244 if !kv_message_parts.is_empty() {
245 kv_message_parts.sort();
246 message = format!("{} {}", message, kv_message_parts.join(", "))
247 }
248
249 message
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn info_formatter() {
258 let svc = Service {
259 name: String::from("test"),
260 version: String::from("0.0.0"),
261 };
262
263 let record = log::Record::builder()
264 .args(format_args!("Info!"))
265 .level(Level::Info)
266 .target("test_app")
267 .file(Some("my_file.rs"))
268 .line(Some(1337))
269 .module_path(Some("my_module"))
270 .build();
271
272 let mut output = format_record(&record, Some(&svc), false);
273 let expected = include_str!("../test_snapshots/info_svc.json");
274 let expected: Value = serde_json::from_str(expected).unwrap();
275
276 assert!(output["eventTime"].as_str().is_some());
278 *output.get_mut("eventTime").unwrap() = json!("2019-09-28T04:00:00.000000000+00:00");
279 assert_eq!(output, expected);
280 }
281
282 #[test]
283 fn error_formatter() {
284 let svc = Service {
285 name: String::from("test"),
286 version: String::from("0.0.0"),
287 };
288
289 let record = log::Record::builder()
290 .args(format_args!("Error!"))
291 .level(Level::Error)
292 .target("test_app")
293 .file(Some("my_file.rs"))
294 .line(Some(1337))
295 .module_path(Some("my_module"))
296 .build();
297
298 let mut output = format_record(&record, None, false);
299 let expected = include_str!("../test_snapshots/no_scv_no_loc.json");
300 let expected: Value = serde_json::from_str(expected).unwrap();
301 assert!(output["eventTime"].as_str().is_some());
302 *output.get_mut("eventTime").unwrap() = json!("2019-09-28T04:00:00.000000000+00:00");
303 assert_eq!(output, expected);
304
305 let mut output = format_record(&record, Some(&svc), true);
306 let expected = include_str!("../test_snapshots/svc_and_loc.json");
307 let expected: Value = serde_json::from_str(expected).unwrap();
308 assert!(output["eventTime"].as_str().is_some());
309 *output.get_mut("eventTime").unwrap() = json!("2019-09-28T04:00:00.000000000+00:00");
310 assert_eq!(output, expected);
311 }
312
313 #[test]
314 #[cfg(feature = "customfields")]
315 fn custom_fields_formatter() {
316 let svc = Service {
317 name: String::from("test"),
318 version: String::from("0.0.0"),
319 };
320
321 let mut map = std::collections::HashMap::new();
322 map.insert("a", "a value");
323 map.insert("b", "b value");
324
325 let record = log::Record::builder()
326 .args(format_args!("Info!"))
327 .level(Level::Info)
328 .target("test_app")
329 .file(Some("my_file.rs"))
330 .line(Some(1337))
331 .module_path(Some("my_module"))
332 .key_values(&mut map)
333 .build();
334
335 let mut output = format_record(&record, Some(&svc), false);
336 let expected = include_str!("../test_snapshots/custom_fields.json");
337 let expected: Value = serde_json::from_str(expected).unwrap();
338
339 assert!(output["eventTime"].as_str().is_some());
341 *output.get_mut("eventTime").unwrap() = json!("2019-09-28T04:00:00.000000000+00:00");
342 assert_eq!(output, expected);
343 }
344
345 #[test]
346 #[cfg(feature = "customfields")]
347 fn custom_fields_formatter_pretty() {
348 let mut map = std::collections::HashMap::new();
349 map.insert("a", "a value");
350 map.insert("b", "b value");
351
352 let record = log::Record::builder()
353 .args(format_args!("Info!"))
354 .level(Level::Info)
355 .target("test_app")
356 .file(Some("my_file.rs"))
357 .line(Some(1337))
358 .module_path(Some("my_module"))
359 .key_values(&mut map)
360 .build();
361
362 let output = format_record_pretty(&record);
363 let expected = "Info! a=a value, b=b value";
364
365 assert_eq!(output, expected);
366 }
367}