Skip to content

Commit a6dfe1a

Browse files
committed
🐛 workaround for a delay, batch,... behavior in otlp exporter and test with fake-opentelemetry-collector (closed too early)
1 parent a7f243c commit a6dfe1a

File tree

7 files changed

+75
-38
lines changed

7 files changed

+75
-38
lines changed

axum-tracing-opentelemetry/src/middleware/trace_extractor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ mod tests {
209209
#[case] headers: &[(&str, &str)],
210210
#[case] is_trace_id_constant: bool,
211211
) {
212-
let fake_env = FakeEnvironment::setup().await;
212+
let mut fake_env = FakeEnvironment::setup().await;
213213
{
214214
let mut svc = Router::new()
215215
.route("/users/:id", get(|| async { StatusCode::OK }))

fake-opentelemetry-collector/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ readme = "README.md"
55
keywords = ["tracing", "opentelemetry", "faker", "mock"]
66
categories = ["development-tools::testing"]
77
edition.workspace = true
8-
version = "0.19.0"
8+
version = "0.20.0"
99
authors.workspace = true
1010
repository.workspace = true
1111
license.workspace = true
@@ -31,11 +31,11 @@ opentelemetry_sdk = { workspace = true, features = [
3131
"testing",
3232
] }
3333
serde = { version = "1.0", features = ["derive"] }
34-
tokio = { version = "1.39", features = ["full"] }
34+
tokio = { version = "1.40", features = ["full"] }
3535
tokio-stream = { version = "0.1", features = ["net"] }
3636
tonic = { workspace = true }
3737
tracing = { workspace = true }
3838

3939
[dev-dependencies]
4040
assert2 = "0.3"
41-
insta = { version = "1.39.0", features = ["yaml", "redactions"] }
41+
insta = { version = "1.39", features = ["yaml", "redactions"] }

fake-opentelemetry-collector/src/lib.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ use logs::*;
88
use trace::*;
99

1010
use std::net::SocketAddr;
11+
use std::time::{Duration, Instant};
1112

1213
use futures::StreamExt;
1314
use opentelemetry::trace::TracerProvider;
1415
use opentelemetry_otlp::WithExportConfig;
1516
use opentelemetry_proto::tonic::collector::logs::v1::logs_service_server::LogsServiceServer;
1617
use opentelemetry_proto::tonic::collector::trace::v1::trace_service_server::TraceServiceServer;
17-
use std::sync::mpsc;
18+
use tokio::sync::mpsc;
19+
use tokio::sync::mpsc::Receiver;
1820
use tokio_stream::wrappers::TcpListenerStream;
1921
use tracing::debug;
2022

@@ -37,8 +39,8 @@ impl FakeCollectorServer {
3739
s
3840
});
3941

40-
let (req_tx, req_rx) = mpsc::sync_channel::<ExportedSpan>(1024);
41-
let (log_tx, log_rx) = mpsc::sync_channel::<ExportedLog>(1024);
42+
let (req_tx, req_rx) = mpsc::channel::<ExportedSpan>(64);
43+
let (log_tx, log_rx) = mpsc::channel::<ExportedLog>(64);
4244
let trace_service = TraceServiceServer::new(FakeTraceService::new(req_tx));
4345
let logs_service = LogsServiceServer::new(FakeLogsService::new(log_tx));
4446
let handle = tokio::task::spawn(async move {
@@ -67,19 +69,31 @@ impl FakeCollectorServer {
6769
format!("http://{}", self.address()) //Devskim: ignore DS137138)
6870
}
6971

70-
pub fn exported_spans(&self) -> Vec<ExportedSpan> {
71-
std::iter::from_fn(|| self.req_rx.try_recv().ok()).collect::<Vec<_>>()
72+
pub async fn exported_spans(
73+
&mut self,
74+
at_least: usize,
75+
timeout: Duration,
76+
) -> Vec<ExportedSpan> {
77+
recv_many(&mut self.req_rx, at_least, timeout).await
7278
}
7379

74-
pub fn exported_logs(&self) -> Vec<ExportedLog> {
75-
std::iter::from_fn(|| self.log_rx.try_recv().ok()).collect::<Vec<_>>()
80+
pub async fn exported_logs(&mut self, at_least: usize, timeout: Duration) -> Vec<ExportedLog> {
81+
recv_many(&mut self.log_rx, at_least, timeout).await
7682
}
7783

7884
pub fn abort(self) {
7985
self.handle.abort()
8086
}
8187
}
8288

89+
async fn recv_many<T>(rx: &mut Receiver<T>, at_least: usize, timeout: Duration) -> Vec<T> {
90+
let deadline = Instant::now();
91+
while rx.len() < at_least && deadline.elapsed() < timeout {
92+
tokio::time::sleep(timeout / 5).await;
93+
}
94+
std::iter::from_fn(|| rx.try_recv().ok()).collect::<Vec<_>>()
95+
}
96+
8397
pub async fn setup_tracer(fake_server: &FakeCollectorServer) -> opentelemetry_sdk::trace::Tracer {
8498
// if the environment variable is set (in test or in caller), `with_endpoint` value is ignored
8599
std::env::remove_var("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT");
@@ -119,7 +133,7 @@ mod tests {
119133

120134
#[tokio::test(flavor = "multi_thread")]
121135
async fn test_fake_tracer_and_collector() {
122-
let fake_collector = FakeCollectorServer::start()
136+
let mut fake_collector = FakeCollectorServer::start()
123137
.await
124138
.expect("fake collector setup and started");
125139
let tracer = setup_tracer(&fake_collector).await;
@@ -133,7 +147,9 @@ mod tests {
133147
span.end();
134148
shutdown_tracer_provider();
135149

136-
let otel_spans = fake_collector.exported_spans();
150+
let otel_spans = fake_collector
151+
.exported_spans(1, Duration::from_millis(2000))
152+
.await;
137153
//insta::assert_debug_snapshot!(otel_spans);
138154
insta::assert_yaml_snapshot!(otel_spans, {
139155
"[].start_time_unix_nano" => "[timestamp]",
@@ -160,7 +176,7 @@ mod tests {
160176

161177
#[tokio::test(flavor = "multi_thread")]
162178
async fn test_fake_logger_and_collector() {
163-
let fake_collector = FakeCollectorServer::start()
179+
let mut fake_collector = FakeCollectorServer::start()
164180
.await
165181
.expect("fake collector setup and started");
166182

@@ -172,7 +188,10 @@ mod tests {
172188
record.set_severity_text("info".into());
173189
logger.emit(record);
174190

175-
let otel_logs = fake_collector.exported_logs();
191+
let otel_logs = fake_collector
192+
.exported_logs(1, Duration::from_millis(500))
193+
.await;
194+
176195
insta::assert_yaml_snapshot!(otel_logs, {
177196
"[].trace_id" => insta::dynamic_redaction(|value, _path| {
178197
assert2::let_assert!(Some(trace_id) = value.as_str());

fake-opentelemetry-collector/src/logs.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use opentelemetry_proto::tonic::collector::logs::v1::{
44
};
55
use serde::Serialize;
66
use std::collections::BTreeMap;
7-
use std::sync::{mpsc, Mutex};
7+
use tokio::sync::mpsc;
88

99
/// This is created to flatten the log record to make it more compatible with insta for testing
1010
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -37,12 +37,12 @@ impl From<opentelemetry_proto::tonic::logs::v1::LogRecord> for ExportedLog {
3737
}
3838

3939
pub(crate) struct FakeLogsService {
40-
tx: Mutex<mpsc::SyncSender<ExportedLog>>,
40+
tx: mpsc::Sender<ExportedLog>,
4141
}
4242

4343
impl FakeLogsService {
44-
pub fn new(tx: mpsc::SyncSender<ExportedLog>) -> Self {
45-
Self { tx: Mutex::new(tx) }
44+
pub fn new(tx: mpsc::Sender<ExportedLog>) -> Self {
45+
Self { tx }
4646
}
4747
}
4848

@@ -52,14 +52,22 @@ impl LogsService for FakeLogsService {
5252
&self,
5353
request: tonic::Request<ExportLogsServiceRequest>,
5454
) -> Result<tonic::Response<ExportLogsServiceResponse>, tonic::Status> {
55-
request
55+
let sender = self.tx.clone();
56+
for el in request
5657
.into_inner()
5758
.resource_logs
5859
.into_iter()
5960
.flat_map(|rl| rl.scope_logs)
6061
.flat_map(|sl| sl.log_records)
6162
.map(ExportedLog::from)
62-
.for_each(|el| self.tx.lock().unwrap().send(el).unwrap());
63+
{
64+
sender
65+
.send(el)
66+
.await
67+
.inspect_err(|e| eprintln!("failed to send to channel: {e}"))
68+
.map_err(|err| tonic::Status::from_error(Box::new(err)))?;
69+
}
70+
6371
Ok(tonic::Response::new(ExportLogsServiceResponse {
6472
partial_success: None,
6573
}))

fake-opentelemetry-collector/src/trace.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ use opentelemetry_proto::tonic::collector::trace::v1::{
55
};
66
use serde::Serialize;
77
use std::collections::BTreeMap;
8-
use std::sync::mpsc;
9-
use std::sync::Mutex;
8+
use tokio::sync::mpsc;
109

1110
use tracing::debug;
1211

@@ -109,12 +108,12 @@ impl From<&opentelemetry_proto::tonic::trace::v1::span::Event> for Event {
109108
}
110109

111110
pub(crate) struct FakeTraceService {
112-
tx: Mutex<mpsc::SyncSender<ExportedSpan>>,
111+
tx: mpsc::Sender<ExportedSpan>,
113112
}
114113

115114
impl FakeTraceService {
116-
pub fn new(tx: mpsc::SyncSender<ExportedSpan>) -> Self {
117-
Self { tx: Mutex::new(tx) }
115+
pub fn new(tx: mpsc::Sender<ExportedSpan>) -> Self {
116+
Self { tx }
118117
}
119118
}
120119

@@ -125,16 +124,21 @@ impl TraceService for FakeTraceService {
125124
request: tonic::Request<ExportTraceServiceRequest>,
126125
) -> Result<tonic::Response<ExportTraceServiceResponse>, tonic::Status> {
127126
debug!("Sending request into channel...");
128-
request
127+
let sender = self.tx.clone();
128+
for es in request
129129
.into_inner()
130130
.resource_spans
131131
.into_iter()
132132
.flat_map(|rs| rs.scope_spans)
133133
.flat_map(|ss| ss.spans)
134134
.map(ExportedSpan::from)
135-
.for_each(|es| {
136-
self.tx.lock().unwrap().send(es).expect("Channel full");
137-
});
135+
{
136+
sender
137+
.send(es)
138+
.await
139+
.inspect_err(|e| eprintln!("failed to send to channel: {e}"))
140+
.map_err(|err| tonic::Status::from_error(Box::new(err)))?;
141+
}
138142
Ok(tonic::Response::new(ExportTraceServiceResponse {
139143
partial_success: None,
140144
}))

testing-tracing-opentelemetry/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license.workspace = true
1414

1515
[dependencies]
1616
assert2 = "0.3"
17-
fake-opentelemetry-collector = { path = "../fake-opentelemetry-collector", version = "0.19" }
17+
fake-opentelemetry-collector = { path = "../fake-opentelemetry-collector", version = "0.20" }
1818
insta = { version = "1.29.0", features = ["yaml", "redactions"] }
1919
opentelemetry = { workspace = true }
2020
opentelemetry_sdk = { workspace = true }

testing-tracing-opentelemetry/src/lib.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,22 @@ impl FakeEnvironment {
123123
}
124124

125125
pub async fn collect_traces(
126-
self,
126+
&mut self,
127127
) -> (Vec<Value>, Vec<fake_opentelemetry_collector::ExportedSpan>) {
128128
opentelemetry::global::shutdown_tracer_provider();
129-
130-
let otel_span = self.fake_collector.exported_spans();
129+
let otel_spans = self
130+
.fake_collector
131+
.exported_spans(1, std::time::Duration::from_millis(5000))
132+
.await;
131133
// insta::assert_debug_snapshot!(first_span);
132-
let tracing_events = std::iter::from_fn(|| self.rx.try_recv().ok())
133-
.map(|bytes| serde_json::from_slice::<Value>(&bytes).unwrap())
134-
.collect::<Vec<_>>();
135-
(tracing_events, otel_span)
134+
let tracing_events = std::iter::from_fn(|| {
135+
self.rx
136+
.recv_timeout(std::time::Duration::from_millis(500))
137+
.ok()
138+
})
139+
.map(|bytes| serde_json::from_slice::<Value>(&bytes).unwrap())
140+
.collect::<Vec<_>>();
141+
(tracing_events, otel_spans)
136142
}
137143
}
138144

0 commit comments

Comments
 (0)