Skip to content

Commit 7be7510

Browse files
authored
add LogfireTracingLayer as a public type (#61)
* move `LogfireTracer` into `LogfireTracingLayer` * add `LogfireTracingLayer` as a public type
1 parent b380ebf commit 7be7510

File tree

3 files changed

+125
-50
lines changed

3 files changed

+125
-50
lines changed

src/bridges/tracing.rs

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::time::SystemTime;
1+
use std::{any::TypeId, time::SystemTime};
22

33
use opentelemetry::{
44
KeyValue,
@@ -9,11 +9,35 @@ use tracing::{Subscriber, field::Visit};
99
use tracing_opentelemetry::{OpenTelemetrySpanExt, OtelData, PreSampledTracer};
1010
use tracing_subscriber::{Layer, registry::LookupSpan};
1111

12-
use crate::{LogfireTracer, try_with_logfire_tracer};
12+
use crate::LogfireTracer;
1313

14-
pub(crate) struct LogfireTracingLayer(pub(crate) opentelemetry_sdk::trace::Tracer);
14+
/// A `tracing` layer that bridges `tracing` spans to OpenTelemetry spans using the Logfire tracer.
15+
///
16+
/// This layer is a wrapper around `tracing_opentelemetry::OpenTelemetryLayer` that adds additional
17+
/// Logfire-specific metadata.
18+
///
19+
/// See [`ShutdownHandler::tracing_layer`][crate::ShutdownHandler::tracing_layer] for how to use
20+
/// this layer.
21+
pub struct LogfireTracingLayer<S> {
22+
tracer: LogfireTracer,
23+
otel_layer: tracing_opentelemetry::OpenTelemetryLayer<S, opentelemetry_sdk::trace::Tracer>,
24+
}
1525

16-
impl<S> Layer<S> for LogfireTracingLayer
26+
impl<S> LogfireTracingLayer<S>
27+
where
28+
S: Subscriber + for<'span> LookupSpan<'span>,
29+
{
30+
/// Create a new `LogfireTracingLayer` with the given tracer.
31+
pub(crate) fn new(tracer: LogfireTracer) -> Self {
32+
let otel_layer = tracing_opentelemetry::layer()
33+
.with_error_records_to_exceptions(true)
34+
.with_tracer(tracer.inner.clone());
35+
36+
LogfireTracingLayer { tracer, otel_layer }
37+
}
38+
}
39+
40+
impl<S> Layer<S> for LogfireTracingLayer<S>
1741
where
1842
S: Subscriber + for<'span> LookupSpan<'span>,
1943
{
@@ -23,6 +47,10 @@ where
2347
id: &tracing::span::Id,
2448
ctx: tracing_subscriber::layer::Context<'_, S>,
2549
) {
50+
// Delegate to OpenTelemetry layer first
51+
self.otel_layer.on_new_span(attrs, id, ctx.clone());
52+
53+
// Add Logfire-specific attributes
2654
let span = ctx.span(id).expect("span not found");
2755
let mut extensions = span.extensions_mut();
2856
if let Some(otel_data) = extensions.get_mut::<OtelData>() {
@@ -46,6 +74,9 @@ where
4674
///
4775
/// e.g. <https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/blob/5830c9113b0d42b72167567bf8e5f4c6b20933c8/axum-tracing-opentelemetry/src/middleware/trace_extractor.rs#L132>
4876
fn on_enter(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
77+
// Delegate to OpenTelemetry layer first
78+
self.otel_layer.on_enter(id, ctx.clone());
79+
4980
let span = ctx.span(id).expect("span not found");
5081
let mut extensions = span.extensions_mut();
5182

@@ -58,7 +89,7 @@ where
5889
// Guaranteed to be on first entering of the span
5990
if let Some(otel_data) = extensions.get_mut::<OtelData>() {
6091
// Emit a pending span, if this span will be sampled.
61-
let context = self.0.sampled_context(otel_data);
92+
let context = self.tracer.inner.sampled_context(otel_data);
6293
let sampling_result = otel_data
6394
.builder
6495
.sampling_result
@@ -110,14 +141,15 @@ where
110141
));
111142
}
112143

113-
pending_span_builder.span_id = Some(self.0.new_span_id());
144+
pending_span_builder.span_id = Some(self.tracer.inner.new_span_id());
114145

115146
let start_time = pending_span_builder
116147
.start_time
117148
.expect("otel SDK sets start time");
118149

119150
// emit pending span
120-
let mut pending_span = pending_span_builder.start_with_context(&self.0, &context);
151+
let mut pending_span =
152+
pending_span_builder.start_with_context(&self.tracer.inner, &context);
121153
pending_span.end_with_timestamp(start_time);
122154
}
123155
}
@@ -131,39 +163,59 @@ where
131163
event: &tracing::Event<'_>,
132164
_ctx: tracing_subscriber::layer::Context<'_, S>,
133165
) {
134-
try_with_logfire_tracer(|tracer| {
135-
// All events are emitted as log spans
136-
emit_event_as_log_span(tracer, event, &tracing::Span::current());
137-
});
166+
// Don't delegate events to OpenTelemetry layer, we emit them as log spans instead.
167+
// FIXME: can we get current span from `ctx`?
168+
emit_event_as_log_span(&self.tracer, event, &tracing::Span::current());
138169
}
139-
}
140170

141-
/// Helper to print spans when dropped; if it was never entered then the pending span
142-
/// is never sent (the console writer uses pending spans).
143-
///
144-
/// This needs to be a separate layer so that it can access the `OtelData` before the
145-
/// `tracing_opentelemetry` layer removes it.
146-
pub struct LogfireTracingPendingSpanNotSentLayer;
171+
fn on_exit(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
172+
self.otel_layer.on_exit(id, ctx);
173+
}
147174

148-
impl<S> Layer<S> for LogfireTracingPendingSpanNotSentLayer
149-
where
150-
S: Subscriber + for<'span> LookupSpan<'span>,
151-
{
152175
fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
153176
let span = ctx.span(&id).expect("span not found");
154-
let mut extensions = span.extensions_mut();
155-
156-
if extensions.get_mut::<LogfirePendingSpanSent>().is_some() {
157-
return;
158-
}
177+
let extensions = span.extensions();
159178

160-
// Guaranteed to be on first entering of the span
161-
if let Some(otel_data) = extensions.get_mut::<OtelData>() {
162-
try_with_logfire_tracer(|tracer| {
163-
if let Some(writer) = &tracer.console_writer {
179+
// We write pending spans to the console; if the pending span was never created then
180+
// we have to manually write it now.
181+
if extensions.get::<LogfirePendingSpanSent>().is_none() {
182+
if let Some(otel_data) = extensions.get::<OtelData>() {
183+
if let Some(writer) = &self.tracer.console_writer {
164184
writer.write_tracing_opentelemetry_data(otel_data);
165185
}
166-
});
186+
}
187+
}
188+
189+
// Delegate to OpenTelemetry layer after handling pending span (it will remove the
190+
// `OtelData` so cannot do before).
191+
drop(extensions);
192+
drop(span);
193+
self.otel_layer.on_close(id, ctx);
194+
}
195+
196+
fn on_follows_from(
197+
&self,
198+
span: &tracing::span::Id,
199+
follows: &tracing::span::Id,
200+
ctx: tracing_subscriber::layer::Context<'_, S>,
201+
) {
202+
self.otel_layer.on_follows_from(span, follows, ctx);
203+
}
204+
205+
fn on_record(
206+
&self,
207+
span: &tracing::span::Id,
208+
values: &tracing::span::Record<'_>,
209+
ctx: tracing_subscriber::layer::Context<'_, S>,
210+
) {
211+
self.otel_layer.on_record(span, values, ctx);
212+
}
213+
214+
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
215+
if id == TypeId::of::<Self>() {
216+
Some(std::ptr::from_ref(self).cast())
217+
} else {
218+
unsafe { self.otel_layer.downcast_raw(id) }
167219
}
168220
}
169221
}

src/lib.rs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ use std::panic::PanicHookInfo;
102102
use std::sync::{Arc, Once};
103103
use std::{backtrace::Backtrace, env::VarError, sync::OnceLock, time::Duration};
104104

105-
use bridges::tracing::LogfireTracingPendingSpanNotSentLayer;
106105
use config::get_base_url_from_token;
107106
use opentelemetry::trace::TracerProvider;
108107
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
@@ -113,7 +112,8 @@ use opentelemetry_sdk::trace::{SdkTracerProvider, Tracer};
113112
use thiserror::Error;
114113
use tracing::Subscriber;
115114
use tracing::level_filters::LevelFilter;
116-
use tracing_subscriber::layer::{Layer, SubscriberExt};
115+
use tracing_subscriber::layer::SubscriberExt;
116+
use tracing_subscriber::registry::LookupSpan;
117117

118118
use crate::bridges::tracing::LogfireTracingLayer;
119119
use crate::config::{
@@ -571,18 +571,15 @@ impl LogfireConfigBuilder {
571571
.with_default_directive(default_level_filter.into())
572572
.from_env()?; // but allow the user to override this with `RUST_LOG`
573573

574+
let tracer = LogfireTracer {
575+
inner: tracer,
576+
handle_panics: self.install_panic_handler,
577+
console_writer,
578+
};
579+
574580
let subscriber = tracing_subscriber::registry()
575581
.with(filter)
576-
.with(LogfireTracingPendingSpanNotSentLayer)
577-
.with(
578-
tracing_opentelemetry::layer()
579-
.with_error_records_to_exceptions(true)
580-
.with_tracer(tracer.clone())
581-
.with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
582-
!metadata.is_event()
583-
})),
584-
)
585-
.with(LogfireTracingLayer(tracer.clone()));
582+
.with(LogfireTracingLayer::new(tracer.clone()));
586583

587584
let mut meter_provider_builder = SdkMeterProvider::builder();
588585

@@ -616,11 +613,7 @@ impl LogfireConfigBuilder {
616613

617614
Ok(LogfireParts {
618615
local: self.local,
619-
tracer: LogfireTracer {
620-
inner: tracer,
621-
handle_panics: self.install_panic_handler,
622-
console_writer,
623-
},
616+
tracer,
624617
subscriber: Arc::new(subscriber),
625618
tracer_provider,
626619
meter_provider,
@@ -661,6 +654,36 @@ impl ShutdownHandler {
661654
.map_err(|e| ConfigureError::Other(e.into()))?;
662655
Ok(())
663656
}
657+
658+
/// Get a tracing layer which can be used to embed this `Logfire` instance into a `tracing_subscriber::Registry`.
659+
///
660+
/// # Example
661+
///
662+
/// ```rust
663+
/// use tracing_subscriber::{Registry, layer::SubscriberExt};
664+
///
665+
/// let shutdown_handler = logfire::configure()
666+
/// .local() // use local mode to avoid setting global state
667+
/// .finish()
668+
/// .expect("Failed to configure logfire");
669+
///
670+
/// let subscriber = tracing_subscriber::registry()
671+
/// .with(shutdown_handler.tracing_layer());
672+
///
673+
/// tracing::subscriber::set_global_default(subscriber)
674+
/// .expect("Failed to set global subscriber");
675+
///
676+
/// logfire::info!("Hello world");
677+
///
678+
/// shutdown_handler.shutdown().expect("Failed to shutdown logfire");
679+
/// ```
680+
#[must_use]
681+
pub fn tracing_layer<S>(&self) -> LogfireTracingLayer<S>
682+
where
683+
S: Subscriber + for<'span> LookupSpan<'span>,
684+
{
685+
LogfireTracingLayer::new(self.tracer.clone())
686+
}
664687
}
665688

666689
struct LogfireParts {

tests/test_basic_exports.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ fn test_basic_span() {
10341034
"code.lineno",
10351035
),
10361036
value: I64(
1037-
693,
1037+
716,
10381038
),
10391039
},
10401040
KeyValue {

0 commit comments

Comments
 (0)