#[doc(hidden)]
pub extern crate env_logger;
use env_logger::Builder;
use log::kv;
use std::{
io::{self, Write},
panic, thread,
};
pub fn init() {
try_init().unwrap()
}
pub fn try_init() -> Result<(), log::SetLoggerError> {
builder().try_init()
}
pub fn panic_hook() {
panic::set_hook(Box::new(|info| {
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
match info.location() {
Some(location) => {
#[cfg(not(feature = "backtrace"))]
{
kv_log_macro::error!(
"panicked at '{}'", msg,
{
thread: thread,
location: format!("{}:{}", location.file(), location.line())
}
);
}
#[cfg(feature = "backtrace")]
{
kv_log_macro::error!(
"panicked at '{}'", msg,
{
thread: thread,
location: format!("{}:{}", location.file(), location.line()),
backtrace: format!("{:?}", backtrace::Backtrace::new())
}
);
}
}
None => {
#[cfg(not(feature = "backtrace"))]
{
kv_log_macro::error!("panicked at '{}'", msg, { thread: thread });
}
#[cfg(feature = "backtrace")]
{
kv_log_macro::error!(
"panicked at '{}'", msg,
{
thread: thread,
backtrace: format!("{:?}", backtrace::Backtrace::new())
}
);
}
}
}
}));
}
pub fn builder() -> Builder {
let mut builder = Builder::from_default_env();
builder.format(write);
builder
}
fn write<F>(
f: &mut F,
record: &log::Record,
) -> io::Result<()>
where
F: Write,
{
write!(f, "{{")?;
write!(f, "\"level\":\"{}\",", record.level())?;
#[cfg(feature = "iso-timestamps")]
{
write!(
f,
"\"ts\":\"{}\"",
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
)?;
}
#[cfg(not(feature = "iso-timestamps"))]
{
write!(
f,
"\"ts\":{}",
std::time::UNIX_EPOCH.elapsed().unwrap().as_millis()
)?;
}
write!(f, ",\"msg\":")?;
write_json_str(f, &record.args().to_string())?;
struct Visitor<'a, W: Write> {
writer: &'a mut W,
}
impl<'kvs, 'a, W: Write> kv::Visitor<'kvs> for Visitor<'a, W> {
fn visit_pair(
&mut self,
key: kv::Key<'kvs>,
val: kv::Value<'kvs>,
) -> Result<(), kv::Error> {
write!(self.writer, ",\"{}\":{}", key, val).unwrap();
Ok(())
}
}
let mut visitor = Visitor { writer: f };
record.key_values().visit(&mut visitor).unwrap();
writeln!(f, "}}")
}
fn write_json_str<W: io::Write>(
writer: &mut W,
raw: &str,
) -> std::io::Result<()> {
serde_json::to_writer(writer, raw)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn writes_records_as_json() -> Result<(), Box<dyn Error>> {
let record = log::Record::builder()
.args(format_args!("hello"))
.level(log::Level::Info)
.build();
let mut buf = Vec::new();
write(&mut buf, &record)?;
let output = std::str::from_utf8(&buf)?;
assert!(serde_json::from_str::<serde_json::Value>(&output).is_ok());
Ok(())
}
#[test]
fn escapes_json_strings() -> Result<(), Box<dyn Error>> {
let mut buf = Vec::new();
write_json_str(
&mut buf, r#""
"#,
)?;
assert_eq!("\"\\\"\\n\\t\"", std::str::from_utf8(&buf)?);
Ok(())
}
}