use std::io;
use tracing::Subscriber;
use tracing_subscriber::{
fmt::{
time::{FormatTime, SystemTime},
MakeWriter,
TestWriter,
},
registry::LookupSpan,
Layer as Subscribe,
Registry,
};
use super::names::{
CURRENT_SPAN,
FIELDS,
FILENAME,
LEVEL,
LINE_NUMBER,
SPAN_LIST,
TARGET,
THREAD_ID,
THREAD_NAME,
TIMESTAMP,
};
use crate::layer::{FlatSchemaKey, JsonLayer};
pub struct Layer<S: for<'lookup> LookupSpan<'lookup> = Registry, W = fn() -> io::Stdout> {
inner: JsonLayer<S, W>,
}
impl<S: Subscriber + for<'lookup> LookupSpan<'lookup>> Default for Layer<S> {
fn default() -> Self {
let mut inner = JsonLayer::stdout();
inner
.with_event(FIELDS)
.with_timer(TIMESTAMP, SystemTime)
.with_target(TARGET)
.with_level(LEVEL)
.with_current_span(CURRENT_SPAN)
.with_span_list(SPAN_LIST);
Self { inner }
}
}
impl<S, W> Subscribe<S> for Layer<S, W>
where
JsonLayer<S, W>: Subscribe<S>,
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_register_dispatch(&self, subscriber: &tracing::Dispatch) {
self.inner.on_register_dispatch(subscriber);
}
fn on_layer(&mut self, subscriber: &mut S) {
self.inner.on_layer(subscriber);
}
fn register_callsite(
&self,
metadata: &'static tracing::Metadata<'static>,
) -> tracing_core::Interest {
self.inner.register_callsite(metadata)
}
fn enabled(
&self,
metadata: &tracing::Metadata<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) -> bool {
self.inner.enabled(metadata, ctx)
}
fn on_new_span(
&self,
attrs: &tracing_core::span::Attributes<'_>,
id: &tracing_core::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
self.inner.on_new_span(attrs, id, ctx);
}
fn on_record(
&self,
span: &tracing_core::span::Id,
values: &tracing_core::span::Record<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
self.inner.on_record(span, values, ctx);
}
fn on_follows_from(
&self,
span: &tracing_core::span::Id,
follows: &tracing_core::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
self.inner.on_follows_from(span, follows, ctx);
}
fn event_enabled(
&self,
event: &tracing::Event<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) -> bool {
self.inner.event_enabled(event, ctx)
}
fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
self.inner.on_event(event, ctx);
}
fn on_enter(
&self,
id: &tracing_core::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
self.inner.on_enter(id, ctx);
}
fn on_exit(&self, id: &tracing_core::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
self.inner.on_exit(id, ctx);
}
fn on_close(&self, id: tracing_core::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
self.inner.on_close(id, ctx);
}
fn on_id_change(
&self,
old: &tracing_core::span::Id,
new: &tracing_core::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
self.inner.on_id_change(old, new, ctx);
}
}
impl<S, W> Layer<S, W>
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
pub fn with_writer<W2>(self, make_writer: W2) -> Layer<S, W2>
where
W2: for<'writer> MakeWriter<'writer> + 'static,
{
Layer::<S, W2> {
inner: self.inner.with_writer(make_writer),
}
}
pub fn map_writer<W2>(self, f: impl FnOnce(W) -> W2) -> Layer<S, W2>
where
W2: for<'writer> MakeWriter<'writer> + 'static,
{
Layer::<S, W2> {
inner: self.inner.map_writer(f),
}
}
pub fn with_test_writer(self) -> Layer<S, TestWriter> {
Layer::<S, TestWriter> {
inner: self.inner.with_test_writer(),
}
}
pub fn writer(&self) -> &W {
self.inner.writer()
}
pub fn writer_mut(&mut self) -> &mut W {
self.inner.writer_mut()
}
pub fn inner_layer_mut(&mut self) -> &mut JsonLayer<S, W> {
&mut self.inner
}
#[must_use]
pub fn log_internal_errors(mut self, log_internal_errors: bool) -> Self {
self.inner.log_internal_errors(log_internal_errors);
self
}
#[must_use]
pub fn flatten_current_span_on_top_level(mut self, flatten_span: bool) -> Self {
if flatten_span {
self.inner.remove_field(CURRENT_SPAN);
self.inner.with_top_level_flattened_current_span();
} else {
self.inner
.remove_flattened_field(&FlatSchemaKey::FlattenedCurrentSpan);
self.inner.with_current_span(CURRENT_SPAN);
}
self
}
#[must_use]
pub fn flatten_span_list_on_top_level(mut self, flatten_span_list: bool) -> Self {
if flatten_span_list {
self.inner.remove_field(SPAN_LIST);
self.inner.with_top_level_flattened_span_list();
} else {
self.inner
.remove_flattened_field(&FlatSchemaKey::FlattenedSpanList);
self.inner.with_span_list(SPAN_LIST);
}
self
}
#[must_use]
pub fn flatten_event(mut self, flatten_event: bool) -> Self {
if flatten_event {
self.inner.remove_field(FIELDS);
self.inner.with_flattened_event();
} else {
self.inner
.remove_flattened_field(&FlatSchemaKey::FlattenedEvent);
self.inner.with_event(FIELDS);
}
self
}
#[must_use]
pub fn with_current_span(mut self, display_current_span: bool) -> Self {
if display_current_span {
self.inner.with_current_span(CURRENT_SPAN);
} else {
self.inner.remove_field(CURRENT_SPAN);
}
self
}
#[must_use]
pub fn with_span_list(mut self, display_span_list: bool) -> Self {
if display_span_list {
self.inner.with_span_list(SPAN_LIST);
} else {
self.inner.remove_field(SPAN_LIST);
}
self
}
#[must_use]
pub fn with_flat_span_list(mut self, flatten_span_list: bool) -> Self {
if flatten_span_list {
self.inner.with_flattened_span_fields(SPAN_LIST);
} else {
self.inner.remove_field(SPAN_LIST);
}
self
}
#[must_use]
pub fn with_timer<T: FormatTime + Send + Sync + 'static>(mut self, timer: T) -> Self {
self.inner.with_timer(TIMESTAMP, timer);
self
}
#[must_use]
pub fn without_time(mut self) -> Self {
self.inner.remove_field(TIMESTAMP);
self
}
#[must_use]
pub fn with_target(mut self, display_target: bool) -> Self {
if display_target {
self.inner.with_target(TARGET);
} else {
self.inner.remove_field(TARGET);
}
self
}
#[must_use]
pub fn with_file(mut self, display_filename: bool) -> Self {
if display_filename {
self.inner.with_file(FILENAME);
} else {
self.inner.remove_field(FILENAME);
}
self
}
#[must_use]
pub fn with_line_number(mut self, display_line_number: bool) -> Self {
if display_line_number {
self.inner.with_line_number(LINE_NUMBER);
} else {
self.inner.remove_field(LINE_NUMBER);
}
self
}
#[must_use]
pub fn with_level(mut self, display_level: bool) -> Self {
if display_level {
self.inner.with_level(LEVEL);
} else {
self.inner.remove_field(LEVEL);
}
self
}
#[must_use]
pub fn with_thread_names(mut self, display_thread_name: bool) -> Self {
if display_thread_name {
self.inner.with_thread_names(THREAD_NAME);
} else {
self.inner.remove_field(THREAD_NAME);
}
self
}
#[must_use]
pub fn with_thread_ids(mut self, display_thread_id: bool) -> Self {
if display_thread_id {
self.inner.with_thread_ids(THREAD_ID);
} else {
self.inner.remove_field(THREAD_ID);
}
self
}
#[cfg(any(
feature = "opentelemetry",
feature = "tracing-opentelemetry-0-28",
feature = "tracing-opentelemetry-0-29",
feature = "tracing-opentelemetry-0-30",
feature = "tracing-opentelemetry-0-31",
feature = "tracing-opentelemetry-0-32",
))]
#[cfg_attr(
docsrs,
doc(any(
feature = "opentelemetry",
feature = "tracing-opentelemetry-0-28",
feature = "tracing-opentelemetry-0-29",
feature = "tracing-opentelemetry-0-30",
feature = "tracing-opentelemetry-0-31",
feature = "tracing-opentelemetry-0-32",
))
)]
#[must_use]
pub fn with_opentelemetry_ids(mut self, display_opentelemetry_ids: bool) -> Self {
self.inner.with_opentelemetry_ids(display_opentelemetry_ids);
self
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use tracing::subscriber::with_default;
use tracing_subscriber::{registry, Layer as _, Registry};
use super::Layer;
use crate::tests::{MockMakeWriter, MockTime};
fn test_json<W, T>(
expected: &serde_json::Value,
layer: Layer<Registry, W>,
producer: impl FnOnce() -> T,
) {
let actual = produce_log_line(layer, producer);
assert_eq!(
expected,
&serde_json::from_str::<serde_json::Value>(&actual).unwrap(),
"expected != actual"
);
}
fn produce_log_line<W, T>(layer: Layer<Registry, W>, producer: impl FnOnce() -> T) -> String {
let make_writer = MockMakeWriter::default();
let collector = layer
.with_writer(make_writer.clone())
.with_timer(MockTime)
.with_subscriber(registry());
with_default(collector, producer);
let buf = make_writer.buf();
dbg!(std::str::from_utf8(&buf[..]).unwrap()).to_owned()
}
#[test]
fn default() {
let expected = json!(
{
"timestamp": "fake time",
"level": "INFO",
"span": {
"answer": 42,
"name": "json_span",
"number": 3,
},
"spans": [
{
"answer": 42,
"name": "json_span",
"number": 3,
},
],
"target": "json_subscriber::fmt::layer::tests",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default();
test_json(&expected, layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn flatten() {
let expected = json!(
{
"timestamp": "fake time",
"level": "INFO",
"span": {
"answer": 42,
"name": "json_span",
"number": 3,
},
"spans": [
{
"answer": 42,
"name": "json_span",
"number": 3,
},
],
"target": "json_subscriber::fmt::layer::tests",
"message": "some json test",
}
);
let layer = Layer::default()
.flatten_event(true)
.with_current_span(true)
.with_span_list(true);
test_json(&expected, layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn flatten_conflict() {
#[rustfmt::skip]
let expected = "{\"level\":\"INFO\",\"timestamp\":\"fake time\",\"level\":\"this is a bug\",\"message\":\"some json test\"}\n";
let layer = Layer::default()
.flatten_event(true)
.with_current_span(false)
.with_span_list(false)
.with_target(false);
let actual = produce_log_line(layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!(level = "this is a bug", "some json test");
});
assert_eq!(expected, actual);
}
#[test]
fn flat_span_list() {
let expected = json!(
{
"timestamp": "fake time",
"level": "INFO",
"spans": {
"answer": 42,
"name": "child_span",
"number": 100,
"text": "text",
},
"target": "json_subscriber::fmt::layer::tests",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default()
.with_flat_span_list(true)
.with_current_span(false);
test_json(&expected, layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
let child =
tracing::info_span!("child_span", number = 100, text = tracing::field::Empty);
let _guard = child.clone().entered();
child.record("text", "text");
tracing::info!("some json test");
});
}
#[test]
fn top_level_flatten_current_span() {
let expected = json!(
{
"timestamp": "fake time",
"level": "INFO",
"name": "child_span",
"number": 100,
"text": "text",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default()
.with_target(false)
.with_span_list(false)
.flatten_current_span_on_top_level(true);
test_json(&expected, layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
let child =
tracing::info_span!("child_span", number = 100, text = tracing::field::Empty);
let _guard = child.clone().entered();
child.record("text", "text");
tracing::info!("some json test");
});
}
#[test]
fn top_level_flatten_span_list() {
let expected = json!(
{
"timestamp": "fake time",
"level": "INFO",
"name": "child_span",
"answer": 42,
"number": 100,
"text": "text",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default()
.with_target(false)
.with_current_span(false)
.flatten_span_list_on_top_level(true);
test_json(&expected, layer, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
let child =
tracing::info_span!("child_span", number = 100, text = tracing::field::Empty);
let _guard = child.clone().entered();
child.record("text", "text");
tracing::info!("some json test");
});
}
#[test]
fn target_quote() {
let expected = json!(
{
"timestamp": "fake time",
"target": "\"",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default()
.with_span_list(false)
.with_current_span(false)
.with_level(false);
test_json(&expected, layer, || {
tracing::info!(target: "\"", "some json test");
});
}
#[test]
fn target_backslash() {
let expected = json!(
{
"timestamp": "fake time",
"target": "\\hello\\\\world\\",
"fields": {
"message": "some json test",
},
}
);
let layer = Layer::default()
.with_span_list(false)
.with_current_span(false)
.with_level(false);
test_json(&expected, layer, || {
tracing::info!(target: "\\hello\\\\world\\", "some json test");
});
}
}