-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
prevent panics when used with per-layer filters (#86)
## Motivation Currently, if the `tracing-opentelemetry` `Layer` has a per-layer filter (in these examples, ones that allow only `>=DEBUG`) the following two cases behave differently: ``` let root = tracing::trace_span!("root"); tracing::debug_span!(parent: &root, "child"); ``` ``` let root = tracing::trace_span!("root"); oot.in_scope(|| tracing::debug_span!("child")); ``` The former panics, and the latter successfully creates a single-span trace. Note that this ONLY happens if there is another layer interested in the `TRACE` level (otherwise, the tracing current-level-filter fast-path filters the root spans out at their callsites). This may seem rare, but it becomes more common when the per-layer filter is a [reload](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/reload/index.html) filter, which means the parent and child spans can be differently filtered if the filter is reloaded between their creation (example: https://github.com/MaterializeInc/materialize/issues/15223) ## Solution Handle this case gracefully. I also did the same in `on_follows_from`
- Loading branch information
Showing
3 changed files
with
172 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use futures_util::future::BoxFuture; | ||
use opentelemetry::trace::TracerProvider as _; | ||
use opentelemetry_sdk::{ | ||
export::trace::{ExportResult, SpanData, SpanExporter}, | ||
trace::{Tracer, TracerProvider}, | ||
}; | ||
use std::sync::{Arc, Mutex}; | ||
use tracing::level_filters::LevelFilter; | ||
use tracing::Subscriber; | ||
use tracing_opentelemetry::layer; | ||
use tracing_subscriber::prelude::*; | ||
|
||
#[derive(Clone, Default, Debug)] | ||
struct TestExporter(Arc<Mutex<Vec<SpanData>>>); | ||
|
||
impl SpanExporter for TestExporter { | ||
fn export(&mut self, mut batch: Vec<SpanData>) -> BoxFuture<'static, ExportResult> { | ||
let spans = self.0.clone(); | ||
Box::pin(async move { | ||
if let Ok(mut inner) = spans.lock() { | ||
inner.append(&mut batch); | ||
} | ||
Ok(()) | ||
}) | ||
} | ||
} | ||
|
||
fn test_tracer() -> (Tracer, TracerProvider, TestExporter, impl Subscriber) { | ||
let exporter = TestExporter::default(); | ||
let provider = TracerProvider::builder() | ||
.with_simple_exporter(exporter.clone()) | ||
.build(); | ||
let tracer = provider.tracer("test"); | ||
|
||
let subscriber = tracing_subscriber::registry() | ||
.with( | ||
layer() | ||
.with_tracer(tracer.clone()) | ||
// DEBUG-level, so `trace_spans` are skipped. | ||
.with_filter(LevelFilter::DEBUG), | ||
) | ||
// This is REQUIRED so that the tracing fast path doesn't filter | ||
// out trace spans at their callsite. | ||
.with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::TRACE)); | ||
|
||
(tracer, provider, exporter, subscriber) | ||
} | ||
|
||
#[test] | ||
fn trace_filtered() { | ||
let (_tracer, provider, exporter, subscriber) = test_tracer(); | ||
|
||
tracing::subscriber::with_default(subscriber, || { | ||
// Neither of these should panic | ||
|
||
let root = tracing::trace_span!("root"); | ||
tracing::debug_span!(parent: &root, "child"); | ||
|
||
let root = tracing::trace_span!("root"); | ||
root.in_scope(|| tracing::debug_span!("child")); | ||
}); | ||
|
||
drop(provider); // flush all spans | ||
let spans = exporter.0.lock().unwrap(); | ||
// Only the child spans are reported. | ||
assert_eq!(spans.len(), 2); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
use futures_util::future::BoxFuture; | ||
use opentelemetry::trace::TracerProvider as _; | ||
use opentelemetry_sdk::{ | ||
export::trace::{ExportResult, SpanData, SpanExporter}, | ||
trace::{Tracer, TracerProvider}, | ||
}; | ||
use std::sync::{Arc, Mutex}; | ||
use tracing::Subscriber; | ||
use tracing_opentelemetry::layer; | ||
use tracing_subscriber::prelude::*; | ||
|
||
#[derive(Clone, Default, Debug)] | ||
struct TestExporter(Arc<Mutex<Vec<SpanData>>>); | ||
|
||
impl SpanExporter for TestExporter { | ||
fn export(&mut self, mut batch: Vec<SpanData>) -> BoxFuture<'static, ExportResult> { | ||
let spans = self.0.clone(); | ||
Box::pin(async move { | ||
if let Ok(mut inner) = spans.lock() { | ||
inner.append(&mut batch); | ||
} | ||
Ok(()) | ||
}) | ||
} | ||
} | ||
|
||
fn test_tracer() -> (Tracer, TracerProvider, TestExporter, impl Subscriber) { | ||
let exporter = TestExporter::default(); | ||
let provider = TracerProvider::builder() | ||
.with_simple_exporter(exporter.clone()) | ||
.build(); | ||
let tracer = provider.tracer("test"); | ||
|
||
// Note that if we added a `with_filter` here, the original bug (issue #14) will | ||
// not reproduce. This is because the `Filtered` layer will not | ||
// call the `tracing-opentelemetry` `Layer`'s `on_follows_from`, as the | ||
// closed followed span no longer exists in a way that can checked against | ||
// the that `Filtered`'s filter. | ||
let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); | ||
|
||
(tracer, provider, exporter, subscriber) | ||
} | ||
|
||
#[test] | ||
fn trace_follows_from_closed() { | ||
let (_tracer, provider, exporter, subscriber) = test_tracer(); | ||
|
||
tracing::subscriber::with_default(subscriber, || { | ||
let f = tracing::debug_span!("f"); | ||
let f_id = f.id().unwrap(); | ||
drop(f); | ||
|
||
let s = tracing::debug_span!("span"); | ||
// This should not panic | ||
s.follows_from(f_id); | ||
}); | ||
|
||
drop(provider); // flush all spans | ||
let spans = exporter.0.lock().unwrap(); | ||
// Only the child spans are reported. | ||
assert_eq!(spans.len(), 2); | ||
} |