summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEliza Weisman <eliza@buoyant.io>2019-03-26 16:43:05 -0700
committerGitHub <noreply@github.com>2019-03-26 16:43:05 -0700
commitd8177f81ac671a7667fb59f24ba9e2cdb7d3fe77 (patch)
treebfb7fdfb12503744948dc6bbb07ef92531f3b4bb
parentceca2a3cd65aadc4f28160c34653bd7d4446ad89 (diff)
trace: Allow trace instrumentation to emit log records (#992)
## Motivation `tokio-trace` currently offers a strategy for compatibility with the `log` crate: its macros can be dropped in as a replacement for `log`'s macros, and a subscriber can be used that translates trace events to log records. However, this requires the application to be aware of `tokio-trace` and manually set up this subscriber. Many libraries currently emit `log` records, and would like to be able to emit `tokio-trace` instrumentation instead. The `tokio` runtimes are one such example. However, with the current log compatibility strategy, replacing existing logging with trace instrumentation would break `tokio`'s logs for any downstream user which is using only `log` and not `tokio-trace`. It is desirable for libraries to have the option to emit both `log` _and_ `tokio-trace` diagnostics from the same instrumentation points. ## Solution This branch adds a `log` feature flag to the `tokio-trace` crate, which when set, causes `tokio-trace` instrumentation to emit log records as well as `tokio-trace` instrumentation. ## Notes In order to allow spans to log their names when they are entered and exited even when the span is disabled, this branch adds an `&'static Metadata` to the `Span` type. This was previously stored in the `Inner` type and was thus only present when the span was enabled. This makes disabled spans one word longer, but enabled spans remain the same size. Fixes: #949 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
-rw-r--r--.cirrus.yml1
-rw-r--r--azure-pipelines.yml1
-rw-r--r--tokio-trace/Cargo.toml11
-rw-r--r--tokio-trace/examples/sloggish/sloggish_subscriber.rs2
-rw-r--r--tokio-trace/src/lib.rs4
-rw-r--r--tokio-trace/src/macros.rs152
-rw-r--r--tokio-trace/src/span.rs190
-rw-r--r--tokio-trace/test-log-support/Cargo.toml10
-rw-r--r--tokio-trace/test-log-support/tests/log_no_trace.rs71
-rw-r--r--tokio-trace/tests/event.rs10
-rw-r--r--tokio-trace/tests/span.rs10
-rw-r--r--tokio-trace/tokio-trace-core/src/field.rs16
12 files changed, 372 insertions, 106 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 053ce41e..922c5bee 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -32,6 +32,7 @@ task:
test_script:
- . $HOME/.cargo/env
- cargo test --all --no-fail-fast
+ - (cd tokio-trace/test-log-support && cargo test)
- (cd tokio-trace/test_static_max_level_features && cargo test)
- cargo doc --all
i686_test_script:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 4170b017..e15e5c55 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -47,6 +47,7 @@ jobs:
- tokio-timer
- tokio-trace
- tokio-trace/tokio-trace-core
+ - tokio-trace/test-log-support
- tokio-trace/test_static_max_level_features
- template: ci/azure-cargo-check.yml
diff --git a/tokio-trace/Cargo.toml b/tokio-trace/Cargo.toml
index 0d4bdcbb..e554fd97 100644
--- a/tokio-trace/Cargo.toml
+++ b/tokio-trace/Cargo.toml
@@ -16,6 +16,7 @@ publish = false
[dependencies]
tokio-trace-core = "0.1"
+log = { version = "0.4", optional = true }
cfg-if = "0.1.7"
[dev-dependencies]
@@ -23,6 +24,11 @@ ansi_term = "0.11"
humantime = "1.1.1"
futures = "0.1"
log = "0.4"
+# These are used for the "basic" example from the tokio-trace-prototype repo,
+# which is currently not included as it used the `tokio-trace-log` crate, and
+# that crate is currently unstable.
+# env_logger = "0.5"
+# tokio-trace-log = { path = "../tokio-trace-log" }
[features]
max_level_off = []
@@ -39,8 +45,3 @@ release_max_level_info = []
release_max_level_debug = []
release_max_level_trace = []
-# These are used for the "basic" example from the tokio-trace-prototype repo,
-# which is currently not included as it used the `tokio-trace-log` crate, and
-# that crate is currently unstable.
-# env_logger = "0.5"
-# tokio-trace-log = { path = "../tokio-trace-log" }
diff --git a/tokio-trace/examples/sloggish/sloggish_subscriber.rs b/tokio-trace/examples/sloggish/sloggish_subscriber.rs
index 6d288644..aed43c83 100644
--- a/tokio-trace/examples/sloggish/sloggish_subscriber.rs
+++ b/tokio-trace/examples/sloggish/sloggish_subscriber.rs
@@ -158,7 +158,7 @@ impl SloggishSubscriber {
stderr: io::stderr(),
stack: Mutex::new(vec![]),
spans: Mutex::new(HashMap::new()),
- ids: AtomicUsize::new(0),
+ ids: AtomicUsize::new(1),
}
}
diff --git a/tokio-trace/src/lib.rs b/tokio-trace/src/lib.rs
index 6182b552..da544c57 100644
--- a/tokio-trace/src/lib.rs
+++ b/tokio-trace/src/lib.rs
@@ -313,6 +313,10 @@
extern crate cfg_if;
extern crate tokio_trace_core;
+#[cfg(feature = "log")]
+#[doc(hidden)]
+pub extern crate log;
+
// Somehow this `use` statement is necessary for us to re-export the `core`
// macros on Rust 1.26.0. I'm not sure how this makes it work, but it does.
#[allow(unused_imports)]
diff --git a/tokio-trace/src/macros.rs b/tokio-trace/src/macros.rs
index 84791747..4dcd677f 100644
--- a/tokio-trace/src/macros.rs
+++ b/tokio-trace/src/macros.rs
@@ -101,7 +101,7 @@ macro_rules! span {
$name:expr,
$($k:ident $( = $val:expr )* ),*
) => {
- if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL {
+ {
use $crate::callsite;
use $crate::callsite::Callsite;
let callsite = callsite! {
@@ -110,18 +110,20 @@ macro_rules! span {
level: $lvl,
fields: $($k),*
};
- if is_enabled!(callsite) {
- let meta = callsite.metadata();
+ let meta = callsite.metadata();
+
+ if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL && is_enabled!(callsite) {
$crate::Span::child_of(
$parent,
meta,
&valueset!(meta.fields(), $($k $( = $val)*),*),
)
} else {
- $crate::Span::new_disabled()
+ __tokio_trace_disabled_span!(
+ meta,
+ &valueset!(meta.fields(), $($k $( = $val)*),*)
+ )
}
- } else {
- $crate::Span::new_disabled()
}
};
(
@@ -130,7 +132,7 @@ macro_rules! span {
$name:expr,
$($k:ident $( = $val:expr )* ),*
) => {
- if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL {
+ {
use $crate::callsite;
use $crate::callsite::Callsite;
let callsite = callsite! {
@@ -139,18 +141,21 @@ macro_rules! span {
level: $lvl,
fields: $($k),*
};
- if is_enabled!(callsite) {
- let meta = callsite.metadata();
+ let meta = callsite.metadata();
+
+ if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL && is_enabled!(callsite) {
$crate::Span::new(
meta,
&valueset!(meta.fields(), $($k $( = $val)*),*),
)
} else {
- $crate::Span::new_disabled()
+ __tokio_trace_disabled_span!(
+ meta,
+ &valueset!(meta.fields(), $($k $( = $val)*),*)
+ )
}
- } else {
- $crate::Span::new_disabled()
}
+
};
(target: $target:expr, level: $lvl:expr, parent: $parent:expr, $name:expr) => {
span!(target: $target, level: $lvl, parent: $parent, $name,)
@@ -338,24 +343,32 @@ macro_rules! span {
#[macro_export(local_inner_macros)]
macro_rules! event {
(target: $target:expr, $lvl:expr, { $( $k:ident = $val:expr ),* $(,)*} )=> ({
- if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL {
- #[allow(unused_imports)]
- use $crate::{callsite, dispatcher, Event, field::{Value, ValueSet}};
- use $crate::callsite::Callsite;
- let callsite = callsite! {
- name: __tokio_trace_concat!(
- "event ",
- __tokio_trace_file!(),
- ":",
- __tokio_trace_line!()
- ),
+ {
+ __tokio_trace_log!(
target: $target,
- level: $lvl,
- fields: $( $k ),*
- };
- if is_enabled!(callsite) {
- let meta = callsite.metadata();
- Event::dispatch(meta, &valueset!(meta.fields(), $( $k = $val),* ));
+ $lvl,
+ $( $k = $val ),*
+ );
+
+ if $lvl <= $crate::level_filters::STATIC_MAX_LEVEL {
+ #[allow(unused_imports)]
+ use $crate::{callsite, dispatcher, Event, field::{Value, ValueSet}};
+ use $crate::callsite::Callsite;
+ let callsite = callsite! {
+ name: __tokio_trace_concat!(
+ "event ",
+ __tokio_trace_file!(),
+ ":",
+ __tokio_trace_line!()
+ ),
+ target: $target,
+ level: $lvl,
+ fields: $( $k ),*
+ };
+ if is_enabled!(callsite) {
+ let meta = callsite.metadata();
+ Event::dispatch(meta, &valueset!(meta.fields(), $( $k = $val),* ));
+ }
}
}
});
@@ -526,7 +539,7 @@ macro_rules! trace {
/// let pos = Position { x: 3.234, y: -1.223 };
///
/// debug!(x = field::debug(pos.x), y = field::debug(pos.y));
-/// debug!(target: "app_events", { position = field::debug(pos) }, "New position");
+/// debug!(target: "app_events", { position = field::debug(&pos) }, "New position");
/// # }
/// ```
#[macro_export(local_inner_macros)]
@@ -1007,3 +1020,82 @@ macro_rules! __tokio_trace_stringify {
stringify!($s)
};
}
+
+#[cfg(feature = "log")]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! level_to_log {
+ ($level:expr) => {
+ match $level {
+ $crate::Level::ERROR => $crate::log::Level::Error,
+ $crate::Level::WARN => $crate::log::Level::Warn,
+ $crate::Level::INFO => $crate::log::Level::Info,
+ $crate::Level::DEBUG => $crate::log::Level::Debug,
+ _ => $crate::log::Level::Trace,
+ }
+ };
+}
+
+#[cfg(feature = "log")]
+#[doc(hidden)]
+#[macro_export(local_inner_macros)]
+macro_rules! __tokio_trace_log {
+ (target: $target:expr, $level:expr, $( $key:ident $( = $val:expr )* ),* $(,)* ) => {
+ use $crate::log;
+ let level = level_to_log!($level);
+ if level <= log::STATIC_MAX_LEVEL {
+ let log_meta = log::Metadata::builder()
+ .level(level)
+ .target($target)
+ .build();
+ let logger = log::logger();
+ if logger.enabled(&log_meta) {
+ logger.log(&log::Record::builder()
+ .file(Some(__tokio_trace_file!()))
+ .module_path(Some(__tokio_trace_module_path!()))
+ .line(Some(__tokio_trace_line!()))
+ .metadata(log_meta)
+ .args(__tokio_trace_format_args!(
+ __tokio_trace_concat!(
+ $(__tokio_trace_log!(@key $key)),*
+ ),
+ $(
+ __tokio_trace_log!(@val_or $key $( = $val)* )
+ ),*
+ ))
+ .build());
+ }
+ }
+ };
+ (@key message) => { "{} " };
+ (@key $key:ident) => { __tokio_trace_concat!(__tokio_trace_stringify!( $key ), "={:?} ") };
+ (@val_or $k:ident = $v:expr) => { $v };
+ (@val_or $k:ident ) => { __tokio_trace_format_args!("?") };
+}
+
+#[cfg(not(feature = "log"))]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __tokio_trace_log {
+ (target: $target:expr, $level:expr, $( $key:ident $( = $val:expr )* ),* $(,)* ) => {};
+}
+
+#[cfg(feature = "log")]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __tokio_trace_disabled_span {
+ ($meta:expr, $valueset:expr) => {{
+ let mut span = $crate::Span::new_disabled($meta);
+ span.record_all(&$valueset);
+ span
+ }};
+}
+
+#[cfg(not(feature = "log"))]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __tokio_trace_disabled_span {
+ ($meta:expr, $valueset:expr) => {
+ $crate::Span::new_disabled($meta)
+ };
+}
diff --git a/tokio-trace/src/span.rs b/tokio-trace/src/span.rs
index bc1aa8e0..822fa620 100644
--- a/tokio-trace/src/span.rs
+++ b/tokio-trace/src/span.rs
@@ -130,10 +130,7 @@ use std::{
cmp, fmt,
hash::{Hash, Hasher},
};
-use {
- dispatcher::{self, Dispatch},
- field, Metadata,
-};
+use {dispatcher::Dispatch, field, Metadata};
/// A handle representing a span, with the capability to enter the span if it
/// exists.
@@ -141,12 +138,13 @@ use {
/// If the span was rejected by the current `Subscriber`'s filter, entering the
/// span will silently do nothing. Thus, the handle can be used in the same
/// manner regardless of whether or not the trace is currently being collected.
-#[derive(Clone, PartialEq, Hash)]
+#[derive(Clone)]
pub struct Span {
/// A handle used to enter the span when it is not executing.
///
/// If this is `None`, then the span has either closed or was never enabled.
- inner: Option<Inner<'static>>,
+ inner: Option<Inner>,
+ meta: &'static Metadata<'static>,
}
/// A handle representing the capacity to enter a span which is known to exist.
@@ -155,7 +153,7 @@ pub struct Span {
/// enabled by the current filter. This type is primarily used for implementing
/// span handles; users should typically not need to interact with it directly.
#[derive(Debug)]
-pub(crate) struct Inner<'a> {
+pub(crate) struct Inner {
/// The span's ID, as provided by `subscriber`.
id: Id,
@@ -164,8 +162,6 @@ pub(crate) struct Inner<'a> {
/// This should be the same subscriber that provided this span with its
/// `id`.
subscriber: Dispatch,
-
- meta: &'a Metadata<'a>,
}
/// A guard representing a span which has been entered and is currently
@@ -178,8 +174,8 @@ pub(crate) struct Inner<'a> {
/// typically not need to interact with it directly.
#[derive(Debug)]
#[must_use = "once a span has been entered, it should be exited"]
-struct Entered<'a> {
- inner: Inner<'a>,
+struct Entered {
+ inner: Inner,
}
// ===== impl Span =====
@@ -244,17 +240,19 @@ impl Span {
/// Constructs a new disabled span.
#[inline(always)]
- pub fn new_disabled() -> Span {
- Span { inner: None }
+ pub fn new_disabled(meta: &'static Metadata<'static>) -> Span {
+ Span { inner: None, meta }
}
- #[inline(always)]
fn make(meta: &'static Metadata<'static>, new_span: Attributes) -> Span {
- let inner = dispatcher::get_default(move |dispatch| {
- let id = dispatch.new_span(&new_span);
- Some(Inner::new(id, dispatch, meta))
+ let attrs = &new_span;
+ let inner = ::dispatcher::get_default(move |dispatch| {
+ let id = dispatch.new_span(attrs);
+ Some(Inner::new(id, dispatch))
});
- Self { inner }
+ let span = Self { inner, meta };
+ span.log(format_args!("{}; {}", meta.name(), FmtAttrs(&new_span)));
+ span
}
/// Executes the given function in the context of this span.
@@ -266,7 +264,8 @@ impl Span {
///
/// Returns the result of evaluating `f`.
pub fn enter<F: FnOnce() -> T, T>(&mut self, f: F) -> T {
- match self.inner.take() {
+ self.log(format_args!("-> {}", self.meta.name));
+ let result = match self.inner.take() {
Some(inner) => {
let guard = inner.enter();
let result = f();
@@ -274,7 +273,9 @@ impl Span {
result
}
None => f(),
- }
+ };
+ self.log(format_args!("<- {}", self.meta.name));
+ result
}
/// Returns a [`Field`](::field::Field) for the field with the given `name`, if
@@ -283,9 +284,7 @@ impl Span {
where
Q: Borrow<str>,
{
- self.inner
- .as_ref()
- .and_then(|inner| inner.meta.fields().field(name))
+ self.metadata().and_then(|meta| meta.fields().field(name))
}
/// Returns true if this `Span` has a field for the given
@@ -305,24 +304,25 @@ impl Span {
Q: field::AsField,
V: field::Value,
{
- if let Some(ref mut inner) = self.inner {
- let meta = inner.metadata();
- if let Some(field) = field.as_field(meta) {
- inner.record(
- &meta
- .fields()
- .value_set(&[(&field, Some(value as &field::Value))]),
- )
- }
+ if let Some(field) = field.as_field(self.meta) {
+ self.record_all(
+ &self
+ .meta
+ .fields()
+ .value_set(&[(&field, Some(value as &field::Value))]),
+ );
}
+
self
}
/// Visit all the fields in the span
pub fn record_all(&mut self, values: &field::ValueSet) -> &mut Self {
+ let record = Record::new(values);
if let Some(ref mut inner) = self.inner {
- inner.record(&values);
+ inner.record(&record);
}
+ self.log(format_args!("{}; {}", self.meta.name(), FmtValues(&record)));
self
}
@@ -361,19 +361,78 @@ impl Span {
/// Returns this span's `Metadata`, if it is enabled.
pub fn metadata(&self) -> Option<&'static Metadata<'static>> {
- self.inner.as_ref().map(Inner::metadata)
+ if self.inner.is_some() {
+ Some(self.meta)
+ } else {
+ None
+ }
+ }
+
+ #[cfg(feature = "log")]
+ #[inline]
+ fn log(&self, message: fmt::Arguments) {
+ use log;
+ let logger = log::logger();
+ let log_meta = log::Metadata::builder()
+ .level(level_to_log!(self.meta.level))
+ .target(self.meta.target)
+ .build();
+ if logger.enabled(&log_meta) {
+ logger.log(
+ &log::Record::builder()
+ .metadata(log_meta)
+ .module_path(self.meta.module_path)
+ .file(self.meta.file)
+ .line(self.meta.line)
+ .args(message)
+ .build(),
+ );
+ }
+ }
+
+ #[cfg(not(feature = "log"))]
+ #[inline]
+ fn log(&self, _: fmt::Arguments) {}
+}
+
+impl cmp::PartialEq for Span {
+ fn eq(&self, other: &Self) -> bool {
+ self.meta.callsite() == other.meta.callsite() && self.inner == other.inner
+ }
+}
+
+impl Hash for Span {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ self.inner.hash(hasher);
}
}
-impl<'a> fmt::Debug for Span {
+impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut span = f.debug_struct("Span");
+ span.field("name", &self.meta.name())
+ .field("level", &self.meta.level())
+ .field("target", &self.meta.target());
+
if let Some(ref inner) = self.inner {
- span.field("id", &inner.id())
+ span.field("id", &inner.id());
} else {
- span.field("disabled", &true)
+ span.field("disabled", &true);
+ }
+
+ if let Some(ref path) = self.meta.module_path() {
+ span.field("module_path", &path);
+ }
+
+ if let Some(ref line) = self.meta.line() {
+ span.field("line", &line);
+ }
+
+ if let Some(ref file) = self.meta.file() {
+ span.field("file", &file);
}
- .finish()
+
+ span.finish()
}
}
@@ -385,14 +444,14 @@ impl<'a> Into<Option<Id>> for &'a Span {
// ===== impl Inner =====
-impl<'a> Inner<'a> {
+impl Inner {
/// Enters the span, returning a guard that may be used to exit the span and
/// re-enter the prior span.
///
/// This is used internally to implement `Span::enter`. It may be used for
/// writing custom span handles, but should generally not be called directly
/// when entering a span.
- fn enter(self) -> Entered<'a> {
+ fn enter(self) -> Entered {
self.subscriber.enter(&self.id);
Entered { inner: self }
}
@@ -421,61 +480,76 @@ impl<'a> Inner<'a> {
self.id.clone()
}
- /// Returns the span's metadata.
- fn metadata(&self) -> &'a Metadata<'a> {
- self.meta
- }
-
- fn record(&mut self, values: &field::ValueSet) {
- if values.callsite() == self.meta.callsite() {
- self.subscriber.record(&self.id, &Record::new(values))
- }
+ fn record(&mut self, values: &Record) {
+ self.subscriber.record(&self.id, values)
}
- fn new(id: Id, subscriber: &Dispatch, meta: &'a Metadata<'a>) -> Self {
+ fn new(id: Id, subscriber: &Dispatch) -> Self {
Inner {
id,
subscriber: subscriber.clone(),
- meta,
}
}
}
-impl<'a> cmp::PartialEq for Inner<'a> {
+impl cmp::PartialEq for Inner {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
-impl<'a> Hash for Inner<'a> {
+impl Hash for Inner {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
-impl<'a> Drop for Inner<'a> {
+impl Drop for Inner {
fn drop(&mut self) {
self.subscriber.drop_span(self.id.clone());
}
}
-impl<'a> Clone for Inner<'a> {
+impl Clone for Inner {
fn clone(&self) -> Self {
Inner {
id: self.subscriber.clone_span(&self.id),
subscriber: self.subscriber.clone(),
- meta: self.meta,
}
}
}
// ===== impl Entered =====
-impl<'a> Entered<'a> {
+impl Entered {
/// Exit the `Entered` guard, returning an `Inner` handle that may be used
/// to re-enter the span.
- fn exit(self) -> Inner<'a> {
+ fn exit(self) -> Inner {
self.inner.subscriber.exit(&self.inner.id);
self.inner
}
}
+
+struct FmtValues<'a>(&'a Record<'a>);
+
+impl<'a> fmt::Display for FmtValues<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut res = Ok(());
+ self.0.record(&mut |k: &field::Field, v: &fmt::Debug| {
+ res = write!(f, "{}={:?} ", k, v);
+ });
+ res
+ }
+}
+
+struct FmtAttrs<'a>(&'a Attributes<'a>);
+
+impl<'a> fmt::Display for FmtAttrs<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut res = Ok(());
+ self.0.record(&mut |k: &field::Field, v: &fmt::Debug| {
+ res = write!(f, "{}={:?} ", k, v);
+ });
+ res
+ }
+}
diff --git a/tokio-trace/test-log-support/Cargo.toml b/tokio-trace/test-log-support/Cargo.toml
new file mode 100644
index 00000000..f749e635
--- /dev/null
+++ b/tokio-trace/test-log-support/Cargo.toml
@@ -0,0 +1,10 @@
+[workspace]
+
+[package]
+name = "test_log_support"
+version = "0.1.0"
+publish = false
+
+[dependencies]
+tokio-trace = { path = "..", features = ["log"] }
+log = { version = "0.4", features = ["std"] }
diff --git a/tokio-trace/test-log-support/tests/log_no_trace.rs b/tokio-trace/test-log-support/tests/log_no_trace.rs
new file mode 100644
index 00000000..25eba9f0
--- /dev/null
+++ b/tokio-trace/test-log-support/tests/log_no_trace.rs
@@ -0,0 +1,71 @@
+extern crate log;
+#[macro_use]
+extern crate tokio_trace;
+
+use log::{LevelFilter, Log, Metadata, Record};
+use std::sync::{Arc, Mutex};
+
+struct State {
+ last_log: Mutex<Option<String>>,
+}
+
+struct Logger(Arc<State>);
+
+impl Log for Logger {
+ fn enabled(&self, _: &Metadata) -> bool {
+ true
+ }
+
+ fn log(&self, record: &Record) {
+ let line = format!("{}", record.args());
+ println!("{:<5} {} {}", record.level(), record.target(), line);
+ *self.0.last_log.lock().unwrap() = Some(line);
+ }
+
+ fn flush(&self) {}
+}
+
+#[test]
+fn test_always_log() {
+ let me = Arc::new(State {
+ last_log: Mutex::new(None),
+ });
+ let a = me.clone();
+ log::set_boxed_logger(Box::new(Logger(me))).unwrap();
+ log::set_max_level(LevelFilter::Trace);
+
+ error!(foo = 5);
+ last(&a, "foo=5");
+ warn!("hello {};", "world");
+ last(&a, "hello world;");
+ info!(message = "hello world;", thingy = 42, other_thingy = 666);
+ last(&a, "hello world; thingy=42 other_thingy=666");
+
+ let mut foo = span!("foo");
+ last(&a, "foo;");
+ foo.enter(|| {
+ last(&a, "-> foo");
+
+ trace!({foo = 3, bar = 4}, "hello {};", "san francisco");
+ last(&a, "hello san francisco; foo=3 bar=4");
+ });
+ last(&a, "<- foo");
+
+ span!("foo", bar = 3, baz = false);
+ last(&a, "foo; bar=3 baz=false");
+
+ let mut span = span!("foo", bar, baz);
+ span.record("bar", &3);
+ last(&a, "foo; bar=3");
+ span.record("baz", &"a string");
+ last(&a, "foo; baz=\"a string\"");
+}
+
+fn last(state: &State, expected: &str) {
+ let mut lock = state.last_log.lock().unwrap();
+ {
+ let last = lock.as_ref().map(|s| s.as_str().trim());
+ assert_eq!(last, Some(expected));
+ }
+ *lock = None;
+}
diff --git a/tokio-trace/tests/event.rs b/tokio-trace/tests/event.rs
index e5fcd818..b2c39cdb 100644
--- a/tokio-trace/tests/event.rs
+++ b/tokio-trace/tests/event.rs
@@ -4,11 +4,7 @@ mod support;
use self::support::*;
-use tokio_trace::{
- field::{debug, display},
- subscriber::with_default,
- Level,
-};
+use tokio_trace::{field::display, subscriber::with_default, Level};
#[test]
fn event_without_message() {
@@ -133,7 +129,11 @@ fn borrowed_field() {
}
#[test]
+// If emitting log instrumentation, this gets moved anyway, breaking the test.
+#[cfg(not(feature = "log"))]
fn move_field_out_of_struct() {
+ use tokio_trace::field::debug;
+
#[derive(Debug)]
struct Position {
x: f32,
diff --git a/tokio-trace/tests/span.rs b/tokio-trace/tests/span.rs
index 1c1bc003..3ff0d95f 100644
--- a/tokio-trace/tests/span.rs
+++ b/tokio-trace/tests/span.rs
@@ -4,11 +4,7 @@ mod support;
use self::support::*;
use std::thread;
-use tokio_trace::{
- field::{debug, display},
- subscriber::with_default,
- Level, Span,
-};
+use tokio_trace::{field::display, subscriber::with_default, Level, Span};
#[test]
fn handles_to_the_same_span_are_equal() {
@@ -307,7 +303,11 @@ fn borrowed_field() {
}
#[test]
+// If emitting log instrumentation, this gets moved anyway, breaking the test.
+#[cfg(not(feature = "log"))]
fn move_field_out_of_struct() {
+ use tokio_trace::field::debug;
+
#[derive(Debug)]
struct Position {
x: f32,
diff --git a/tokio-trace/tokio-trace-core/src/field.rs b/tokio-trace/tokio-trace-core/src/field.rs
index 84b01543..0d85e671 100644
--- a/tokio-trace/tokio-trace-core/src/field.rs
+++ b/tokio-trace/tokio-trace-core/src/field.rs
@@ -222,11 +222,11 @@ pub trait Value: ::sealed::Sealed {
}
/// A `Value` which serializes as a string using `fmt::Display`.
-#[derive(Debug, Clone)]
+#[derive(Clone)]
pub struct DisplayValue<T: fmt::Display>(T);
/// A `Value` which serializes as a string using `fmt::Debug`.
-#[derive(Debug, Clone)]
+#[derive(Clone)]
pub struct DebugValue<T: fmt::Debug>(T);
/// Wraps a type implementing `fmt::Display` as a `Value` that can be
@@ -360,6 +360,12 @@ where
}
}
+impl<T: fmt::Display> fmt::Debug for DisplayValue<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
// ===== impl DebugValue =====
impl<T: fmt::Debug> ::sealed::Sealed for DebugValue<T> {}
@@ -373,6 +379,12 @@ where
}
}
+impl<T: fmt::Debug> fmt::Debug for DebugValue<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self.0)
+ }
+}
+
// ===== impl Field =====
impl Field {