Skip to content

Commit a0a8c23

Browse files
committed
feat: add an experimental on_ending api
This API allows to mutations of the span when it is ending. It's marked as on development in the spec, but it is useful for span obfuscation for example, which needs to done after attributes can added to the span anymore.
1 parent ac0c3bd commit a0a8c23

File tree

5 files changed

+84
-17
lines changed

5 files changed

+84
-17
lines changed

examples/tracing-http-propagator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ hyper = { workspace = true, features = ["full"] }
2424
hyper-util = { workspace = true, features = ["full"] }
2525
tokio = { workspace = true, features = ["full"] }
2626
opentelemetry = { path = "../../opentelemetry" }
27-
opentelemetry_sdk = { path = "../../opentelemetry-sdk" }
27+
opentelemetry_sdk = { path = "../../opentelemetry-sdk", features = ["experimental_span_processor_on_ending"]}
2828
opentelemetry-http = { path = "../../opentelemetry-http" }
2929
opentelemetry-stdout = { workspace = true, features = ["trace", "logs"] }
3030
opentelemetry-semantic-conventions = { path = "../../opentelemetry-semantic-conventions" }

examples/tracing-http-propagator/src/server.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,44 @@ impl SpanProcessor for RouteConcurrencyCounterSpanProcessor {
166166
}
167167
}
168168

169+
fn obfuscate_http_auth_url(s: &str) -> Option<String> {
170+
let uri = hyper::http::Uri::from_maybe_shared(s.to_owned()).ok()?;
171+
let authority = uri.authority()?;
172+
let (_, url) = authority.as_str().split_once('@')?;
173+
let new_auth = format!("REDACTED_USERNAME:REDACTED_PASSWORD@{url}");
174+
let mut parts = uri.into_parts();
175+
parts.authority = Some(hyper::http::uri::Authority::from_maybe_shared(new_auth).ok()?);
176+
Some(hyper::Uri::from_parts(parts).ok()?.to_string())
177+
}
178+
179+
#[derive(Debug)]
180+
/// A custom span processor that uses on_ending to obfuscate sensitive information in span attributes.
181+
///
182+
/// Currently this only overrides http auth information in the URI.
183+
struct SpanOnbfuscationProcessor;
184+
185+
impl SpanProcessor for SpanOnbfuscationProcessor {
186+
fn force_flush(&self) -> OTelSdkResult {
187+
Ok(())
188+
}
189+
190+
fn shutdown(&self) -> OTelSdkResult {
191+
Ok(())
192+
}
193+
fn on_start(&self, _span: &mut opentelemetry_sdk::trace::Span, _cx: &Context) {}
194+
195+
fn on_ending(&self, span: &mut opentelemetry_sdk::trace::Span) {
196+
let mut obfuscated_attributes = Vec::new();
197+
for KeyValue { key, value, .. } in span.attributes() {
198+
if let Some(redacted_uri) = obfuscate_http_auth_url(value.as_str().as_ref()) {
199+
obfuscated_attributes.push((key.clone(), KeyValue::new(key.clone(), redacted_uri)));
200+
}
201+
}
202+
}
203+
204+
fn on_end(&self, _span: &mut FinishedSpan) {}
205+
}
206+
169207
/// A custom log processor that enriches LogRecords with baggage attributes.
170208
/// Baggage information is not added automatically without this processor.
171209
#[derive(Debug)]
@@ -225,6 +263,7 @@ fn init_tracer() -> SdkTracerProvider {
225263
let provider = SdkTracerProvider::builder()
226264
.with_span_processor(RouteConcurrencyCounterSpanProcessor::default())
227265
.with_span_processor(EnrichWithBaggageSpanProcessor)
266+
.with_span_processor(SpanOnbfuscationProcessor)
228267
.with_simple_exporter(SpanExporter::default())
229268
.build();
230269

opentelemetry-sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ experimental_logs_batch_log_processor_with_async_runtime = ["logs", "experimenta
5858
experimental_logs_concurrent_log_processor = ["logs"]
5959
experimental_trace_batch_span_processor_with_async_runtime = ["trace", "experimental_async_runtime"]
6060
experimental_metrics_disable_name_validation = ["metrics"]
61+
experimental_span_processor_on_ending = ["trace"]
6162

6263
[[bench]]
6364
name = "context"

opentelemetry-sdk/src/trace/span.rs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -198,33 +198,47 @@ impl opentelemetry::trace::Span for Span {
198198

199199
impl Span {
200200
fn ensure_ended_and_exported(&mut self, timestamp: Option<SystemTime>) {
201+
match self.data {
202+
Some(ref mut data) => {
203+
// ensure end time is set via explicit end or implicitly on drop
204+
if let Some(timestamp) = timestamp {
205+
data.end_time = timestamp;
206+
} else if data.end_time == data.start_time {
207+
data.end_time = opentelemetry::time::now();
208+
}
209+
}
210+
None => {
211+
return;
212+
}
213+
};
214+
215+
if self.tracer.provider().is_shutdown() {
216+
return;
217+
}
218+
let provider = self.tracer.provider().clone();
219+
220+
#[cfg(feature = "experimental_span_processor_on_ending")]
221+
{
222+
for processor in provider.span_processors() {
223+
processor.on_ending( self);
224+
}
225+
}
226+
201227
let Span {
202228
data,
203229
tracer,
204230
span_context,
205231
..
206232
} = self;
233+
207234
// skip if data has already been exported
208-
let mut data = match data.take() {
235+
let data = match data.take() {
209236
Some(data) => data,
210237
None => return,
211238
};
212239
let span_context: SpanContext =
213240
std::mem::replace(span_context, SpanContext::empty_context());
214241

215-
let provider = tracer.provider();
216-
// skip if provider has been shut down
217-
if provider.is_shutdown() {
218-
return;
219-
}
220-
221-
// ensure end time is set via explicit end or implicitly on drop
222-
if let Some(timestamp) = timestamp {
223-
data.end_time = timestamp;
224-
} else if data.end_time == data.start_time {
225-
data.end_time = opentelemetry::time::now();
226-
}
227-
228242
let mut finished_span = FinishedSpan {
229243
span: Some(build_export_data(data, span_context, tracer)),
230244
is_last_processor: false,

opentelemetry-sdk/src/trace/span_processor.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,23 @@ pub trait SpanProcessor: Send + Sync + std::fmt::Debug {
7979
/// synchronously on the thread that started the span, therefore it should
8080
/// not block or throw exceptions.
8181
fn on_start(&self, span: &mut Span, cx: &Context);
82+
#[cfg(feature = "experimental_span_processor_on_ending")]
83+
/// `on_ending` is called when a `Span` is ending. The en timestampe has already
84+
/// been computed.
85+
/// Mutations done to the span in this method will be reflected in the span passed
86+
/// to other span processors.
87+
/// This method is called synchronously within the `Span::end` API, therefore it
88+
/// should not block or throw an exception.
89+
/// If multiple SpanProcessors are registered, their on_ending methods are invoked
90+
/// in the order they have been registered.
91+
fn on_ending(&self, _span: &mut Span) {
92+
// Default implementation is a no-op so existing processor implementations
93+
// don't break if this feature in enabled transitively.
94+
}
95+
8296
/// `on_end` is called after a `Span` is ended (i.e., the end timestamp is
8397
/// already set). This method is called synchronously within the `Span::end`
8498
/// API, therefore it should not block or throw an exception.
85-
/// TODO - This method should take reference to `SpanData`
8699
fn on_end(&self, span: &mut FinishedSpan);
87100
/// Force the spans lying in the cache to be exported.
88101
fn force_flush(&self) -> OTelSdkResult;
@@ -870,8 +883,8 @@ mod tests {
870883
OTEL_BSP_EXPORT_TIMEOUT_DEFAULT, OTEL_BSP_MAX_CONCURRENT_EXPORTS,
871884
OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT, OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT,
872885
};
873-
use crate::trace::{FinishedSpan, InMemorySpanExporterBuilder};
874886
use crate::trace::{BatchConfig, BatchConfigBuilder, SpanEvents, SpanLinks};
887+
use crate::trace::{FinishedSpan, InMemorySpanExporterBuilder};
875888
use crate::trace::{SpanData, SpanExporter};
876889
use opentelemetry::trace::{SpanContext, SpanId, SpanKind, Status};
877890
use std::fmt::Debug;

0 commit comments

Comments
 (0)