#![doc(html_root_url = "https://docs.rs/tracing-log/0.0.1-alpha.1")]
#![deny(missing_debug_implementations, unreachable_pub)]
#![cfg_attr(test, deny(warnings))]
use lazy_static::lazy_static;
use std::{fmt, io};
use tracing_core::{
callsite::{self, Callsite},
dispatcher,
field::{self, Field, Visit},
identify_callsite,
metadata::{Kind, Level},
subscriber, Event, Metadata,
};
pub mod log_tracer;
pub mod trace_logger;
#[doc(inline)]
pub use self::{log_tracer::LogTracer, trace_logger::TraceLogger};
pub fn format_trace(record: &log::Record) -> io::Result<()> {
let filter_meta = record.as_trace();
if !dispatcher::get_default(|dispatch| dispatch.enabled(&filter_meta)) {
return Ok(());
};
let (cs, keys) = loglevel_to_cs(record.level());
let log_module = record.module_path();
let log_file = record.file();
let log_line = record.line();
let module = log_module.as_ref().map(|s| s as &dyn field::Value);
let file = log_file.as_ref().map(|s| s as &dyn field::Value);
let line = log_line.as_ref().map(|s| s as &dyn field::Value);
let meta = cs.metadata();
Event::dispatch(
&meta,
&meta.fields().value_set(&[
(&keys.message, Some(record.args() as &dyn field::Value)),
(&keys.target, Some(&record.target())),
(&keys.module, module),
(&keys.file, file),
(&keys.line, line),
]),
);
Ok(())
}
pub trait AsLog {
type Log;
fn as_log(&self) -> Self::Log;
}
pub trait AsTrace {
type Trace;
fn as_trace(&self) -> Self::Trace;
}
impl<'a> AsLog for Metadata<'a> {
type Log = log::Metadata<'a>;
fn as_log(&self) -> Self::Log {
log::Metadata::builder()
.level(self.level().as_log())
.target(self.target())
.build()
}
}
struct Fields {
message: field::Field,
target: field::Field,
module: field::Field,
file: field::Field,
line: field::Field,
}
static FIELD_NAMES: &'static [&'static str] = &[
"message",
"log.target",
"log.module_path",
"log.file",
"log.line",
];
impl Fields {
fn new(cs: &'static dyn Callsite) -> Self {
let fieldset = cs.metadata().fields();
let message = fieldset.field("message").unwrap();
let target = fieldset.field("log.target").unwrap();
let module = fieldset.field("log.module_path").unwrap();
let file = fieldset.field("log.file").unwrap();
let line = fieldset.field("log.line").unwrap();
Fields {
message,
target,
module,
file,
line,
}
}
}
macro_rules! log_cs {
($level:expr) => {{
struct Callsite;
static META: Metadata = Metadata::new(
"log event",
"log",
$level,
None,
None,
None,
field::FieldSet::new(FIELD_NAMES, identify_callsite!(&Callsite)),
Kind::EVENT,
);
impl callsite::Callsite for Callsite {
fn set_interest(&self, _: subscriber::Interest) {}
fn metadata(&self) -> &'static Metadata<'static> {
&META
}
}
&Callsite
}};
}
static TRACE_CS: &'static dyn Callsite = log_cs!(tracing_core::Level::TRACE);
static DEBUG_CS: &'static dyn Callsite = log_cs!(tracing_core::Level::DEBUG);
static INFO_CS: &'static dyn Callsite = log_cs!(tracing_core::Level::INFO);
static WARN_CS: &'static dyn Callsite = log_cs!(tracing_core::Level::WARN);
static ERROR_CS: &'static dyn Callsite = log_cs!(tracing_core::Level::ERROR);
lazy_static! {
static ref TRACE_FIELDS: Fields = Fields::new(TRACE_CS);
static ref DEBUG_FIELDS: Fields = Fields::new(DEBUG_CS);
static ref INFO_FIELDS: Fields = Fields::new(INFO_CS);
static ref WARN_FIELDS: Fields = Fields::new(WARN_CS);
static ref ERROR_FIELDS: Fields = Fields::new(ERROR_CS);
}
fn level_to_cs(level: &Level) -> (&'static dyn Callsite, &'static Fields) {
match *level {
Level::TRACE => (TRACE_CS, &*TRACE_FIELDS),
Level::DEBUG => (DEBUG_CS, &*DEBUG_FIELDS),
Level::INFO => (INFO_CS, &*INFO_FIELDS),
Level::WARN => (WARN_CS, &*WARN_FIELDS),
Level::ERROR => (ERROR_CS, &*ERROR_FIELDS),
}
}
fn loglevel_to_cs(level: log::Level) -> (&'static dyn Callsite, &'static Fields) {
match level {
log::Level::Trace => (TRACE_CS, &*TRACE_FIELDS),
log::Level::Debug => (DEBUG_CS, &*DEBUG_FIELDS),
log::Level::Info => (INFO_CS, &*INFO_FIELDS),
log::Level::Warn => (WARN_CS, &*WARN_FIELDS),
log::Level::Error => (ERROR_CS, &*ERROR_FIELDS),
}
}
impl<'a> AsTrace for log::Record<'a> {
type Trace = Metadata<'a>;
fn as_trace(&self) -> Self::Trace {
let cs_id = identify_callsite!(loglevel_to_cs(self.level()).0);
Metadata::new(
"log record",
self.target(),
self.level().as_trace(),
self.file(),
self.line(),
self.module_path(),
field::FieldSet::new(FIELD_NAMES, cs_id),
Kind::EVENT,
)
}
}
impl AsLog for tracing_core::Level {
type Log = log::Level;
fn as_log(&self) -> log::Level {
match *self {
tracing_core::Level::ERROR => log::Level::Error,
tracing_core::Level::WARN => log::Level::Warn,
tracing_core::Level::INFO => log::Level::Info,
tracing_core::Level::DEBUG => log::Level::Debug,
tracing_core::Level::TRACE => log::Level::Trace,
}
}
}
impl AsTrace for log::Level {
type Trace = tracing_core::Level;
fn as_trace(&self) -> tracing_core::Level {
match self {
log::Level::Error => tracing_core::Level::ERROR,
log::Level::Warn => tracing_core::Level::WARN,
log::Level::Info => tracing_core::Level::INFO,
log::Level::Debug => tracing_core::Level::DEBUG,
log::Level::Trace => tracing_core::Level::TRACE,
}
}
}
pub trait NormalizeEvent<'a> {
fn normalized_metadata(&'a self) -> Option<Metadata<'a>>;
fn is_log(&self) -> bool;
}
impl<'a> NormalizeEvent<'a> for Event<'a> {
fn normalized_metadata(&'a self) -> Option<Metadata<'a>> {
let original = self.metadata();
if self.is_log() {
let mut fields = LogVisitor::new_for(self, level_to_cs(original.level()).1);
self.record(&mut fields);
Some(Metadata::new(
"log event",
fields.target.unwrap_or("log"),
original.level().clone(),
fields.file,
fields.line.map(|l| l as u32),
fields.module_path,
field::FieldSet::new(&["message"], original.callsite()),
Kind::EVENT,
))
} else {
None
}
}
fn is_log(&self) -> bool {
self.metadata().callsite() == identify_callsite!(level_to_cs(self.metadata().level()).0)
}
}
struct LogVisitor<'a> {
target: Option<&'a str>,
module_path: Option<&'a str>,
file: Option<&'a str>,
line: Option<u64>,
fields: &'static Fields,
}
impl<'a> LogVisitor<'a> {
fn new_for(_event: &'a Event<'a>, fields: &'static Fields) -> Self {
Self {
target: None,
module_path: None,
file: None,
line: None,
fields,
}
}
}
impl<'a> Visit for LogVisitor<'a> {
fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {}
fn record_u64(&mut self, field: &Field, value: u64) {
if field == &self.fields.line {
self.line = Some(value);
}
}
fn record_str(&mut self, field: &Field, value: &str) {
unsafe {
if field == &self.fields.file {
self.file = Some(&*(value as *const _));
} else if field == &self.fields.target {
self.target = Some(&*(value as *const _));
} else if field == &self.fields.module {
self.module_path = Some(&*(value as *const _));
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn test_callsite(level: log::Level) {
let record = log::Record::builder()
.args(format_args!("Error!"))
.level(level)
.target("myApp")
.file(Some("server.rs"))
.line(Some(144))
.module_path(Some("server"))
.build();
let meta = record.as_trace();
let (cs, _keys) = loglevel_to_cs(record.level());
let cs_meta = cs.metadata();
assert_eq!(meta.callsite(), cs_meta.callsite());
assert_eq!(meta.level(), &level.as_trace());
}
#[test]
fn log_callsite_is_correct() {
test_callsite(log::Level::Error);
test_callsite(log::Level::Warn);
test_callsite(log::Level::Info);
test_callsite(log::Level::Debug);
test_callsite(log::Level::Trace);
}
}