diff --git a/Cargo.toml b/Cargo.toml index f1cbc87..cafdc82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ timestamp = ["chrono"] [dependencies] chrono = { version = "0.4", optional = true } libc = { version = "0.2.58" } -log = { version = "0.4", features = ["std"] } +log = { version = "0.4.8", features = ["std", "kv_unstable"] } log-panics = { version = "2", optional = true, features = ["with-backtrace"] } [dev-dependencies] diff --git a/examples/key_value.rs b/examples/key_value.rs new file mode 100644 index 0000000..0c527e3 --- /dev/null +++ b/examples/key_value.rs @@ -0,0 +1,81 @@ +use log; +use std_logger::REQUEST_TARGET; + +fn main() { + // Initialize the logger. + std_logger::init(); + + // Fake the handling of a request. + logger_middleware(Request { + url: "/".to_owned(), + method: "GET".to_owned(), + }); +} + +// Our fake HTTP request. +struct Request { + url: String, + method: String, +} + +// Our fake HTTP response. +struct Response { + status_code: u16, + body: String, +} + +fn logger_middleware(request: Request) -> Response { + // Clone the url and method. Note: don't actually do this in an HTTP this is + // rather wastefull to. + let url = request.url.clone(); + let method = request.url.clone(); + + // Call our handler. + let response = http_handler(request); + + log::info!("Hello world"); + + let kvs: Vec> = vec![ + Box::new(("url", &url)), + Box::new(("method", &method)), + Box::new(("status_code", response.status_code)), + Box::new(("body_size", response.body.len() as u64)), + ]; + + let record = log::Record::builder() + .args(format_args!("got request")) + .level(log::Level::Info) + .target(REQUEST_TARGET) + .file(Some(file!())) + .line(Some(line!())) + .module_path(Some(module_path!())) + .key_values(&kvs) + .build(); + log::logger().log(&record); + + let record = log::Record::builder() + .args(format_args!("some message")) + .level(log::Level::Info) + .target("some_target") + .file(Some(file!())) + .line(Some(line!())) + .module_path(Some(module_path!())) + .key_values(&("single", "value")) + .build(); + log::logger().log(&record); + + response +} + +fn http_handler(request: Request) -> Response { + match (request.method.as_str(), request.url.as_str()) { + ("GET", "/") => Response { + status_code: 200, + body: "Home page".to_owned(), + }, + _ => Response { + status_code: 404, + body: "Not found".to_owned(), + }, + } +} diff --git a/src/lib.rs b/src/lib.rs index 83505c0..bf17c3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,10 +222,10 @@ )] use std::cell::RefCell; -use std::env; use std::io::{self, Write}; +use std::{env, fmt}; -use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; +use log::{kv, LevelFilter, Log, Metadata, Record, SetLoggerError}; #[cfg(feature = "timestamp")] use chrono::{Datelike, Timelike}; @@ -370,6 +370,35 @@ impl Log for Logger { fn flush(&self) {} } +/// Prints key values in ": key1=value1, key2=value2" format. +/// +/// # Notes +/// +/// Prints ": " itself, only when there is at least one key value pair. +struct KeyValuePrinter<'a>(&'a dyn kv::Source); + +impl<'a> fmt::Display for KeyValuePrinter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0 + .visit(&mut KeyValueVisitor(true, f)) + .map_err(|_| fmt::Error) + } +} + +struct KeyValueVisitor<'a, 'b>(bool, &'a mut fmt::Formatter<'b>); + +impl<'a, 'b, 'kvs> kv::Visitor<'kvs> for KeyValueVisitor<'a, 'b> { + fn visit_pair(&mut self, key: kv::Key<'kvs>, value: kv::Value<'kvs>) -> Result<(), kv::Error> { + self.1 + .write_str(if self.0 { ": " } else { ", " }) + .and_then(|()| { + self.0 = false; + write!(self.1, "{}={}", key, value) + }) + .map_err(Into::into) + } +} + /// The actual logging of a record. fn log(record: &Record) { // Thread local buffer for logging. This way we only lock standard out/error @@ -401,17 +430,24 @@ fn log(record: &Record) { match record.target() { REQUEST_TARGET => { - writeln!(&mut buffer, "[REQUEST]: {}", record.args()).unwrap_or_else(log_failure); + writeln!( + &mut buffer, + "[REQUEST]: {}{}", + record.args(), + KeyValuePrinter(record.key_values()) + ) + .unwrap_or_else(log_failure); write_once(stdout(), &buffer).unwrap_or_else(log_failure); } target => { writeln!( &mut buffer, - "[{}] {}: {}", + "[{}] {}: {}{}", record.level(), target, - record.args() + record.args(), + KeyValuePrinter(record.key_values()) ) .unwrap_or_else(log_failure);