Files
jcoffey-dev 1e7e8dfd48
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 7m26s
Vendor upstream's opentelemetry-rust fork at the pinned commit
2026-10-27 22:55:35 -07:00

244 lines
8.2 KiB
Rust

use futures_util::StreamExt;
use opentelemetry::global;
use opentelemetry::trace::{Span, SpanKind, Tracer};
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
use opentelemetry_proto::tonic::collector::trace::v1::{
trace_service_server::{TraceService, TraceServiceServer},
ExportTraceServiceRequest, ExportTraceServiceResponse,
};
use std::{net::SocketAddr, sync::Mutex};
use tokio::sync::mpsc;
use tokio_stream::wrappers::TcpListenerStream;
#[cfg(feature = "gzip-tonic")]
use tonic::codec::CompressionEncoding;
struct MockServer {
tx: Mutex<mpsc::Sender<ExportTraceServiceRequest>>,
}
impl MockServer {
pub fn new(tx: mpsc::Sender<ExportTraceServiceRequest>) -> Self {
Self { tx: Mutex::new(tx) }
}
}
#[tonic::async_trait]
impl TraceService for MockServer {
async fn export(
&self,
request: tonic::Request<ExportTraceServiceRequest>,
) -> Result<tonic::Response<ExportTraceServiceResponse>, tonic::Status> {
println!("Sending request into channel...");
// assert we have required metadata key
assert_eq!(
request.metadata().get("x-header-key"),
Some(&("header-value".parse().unwrap()))
);
self.tx
.lock()
.unwrap()
.try_send(request.into_inner())
.expect("Channel full");
Ok(tonic::Response::new(ExportTraceServiceResponse {
partial_success: None,
}))
}
}
async fn setup() -> (SocketAddr, mpsc::Receiver<ExportTraceServiceRequest>) {
let addr: SocketAddr = "[::1]:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("failed to bind");
let addr = listener.local_addr().unwrap();
let stream = TcpListenerStream::new(listener).map(|s| {
if let Ok(ref s) = s {
println!("Got new conn at {}", s.peer_addr().unwrap());
}
s
});
let (req_tx, req_rx) = mpsc::channel(10);
#[cfg(feature = "gzip-tonic")]
let service = TraceServiceServer::new(MockServer::new(req_tx))
.accept_compressed(CompressionEncoding::Gzip);
#[cfg(not(feature = "gzip-tonic"))]
let service = TraceServiceServer::new(MockServer::new(req_tx));
tokio::task::spawn(async move {
tonic::transport::Server::builder()
.add_service(service)
.serve_with_incoming(stream)
.await
.expect("Server failed");
});
(addr, req_rx)
}
#[tokio::test(flavor = "multi_thread")]
async fn smoke_tracer() {
println!("Starting server setup...");
let (addr, mut req_rx) = setup().await;
{
println!("Installing tracer provider...");
let mut metadata = tonic::metadata::MetadataMap::new();
metadata.insert("x-header-key", "header-value".parse().unwrap());
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(
#[cfg(feature = "gzip-tonic")]
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_compression(opentelemetry_otlp::Compression::Gzip)
.with_endpoint(format!("http://{addr}"))
.with_metadata(metadata)
.build()
.expect("gzip-tonic SpanExporter failed to build"),
#[cfg(not(feature = "gzip-tonic"))]
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(format!("http://{}", addr))
.with_metadata(metadata)
.build()
.expect("NON gzip-tonic SpanExporter failed to build"),
)
.build();
global::set_tracer_provider(tracer_provider.clone());
let tracer = global::tracer("smoke");
println!("Sending span...");
let mut span = tracer
.span_builder("my-test-span")
.with_kind(SpanKind::Server)
.start(&tracer);
span.add_event("my-test-event", vec![]);
span.end();
tracer_provider
.shutdown()
.expect("tracer_provider should shutdown successfully");
}
println!("Waiting for request...");
let req = req_rx.recv().await.expect("missing export request");
let first_span = req
.resource_spans
.first()
.unwrap()
.scope_spans
.first()
.unwrap()
.spans
.first()
.unwrap();
assert_eq!("my-test-span", first_span.name);
let first_event = first_span.events.first().unwrap();
assert_eq!("my-test-event", first_event.name);
}
// Smoke test for partial success handling. We set everything up and push some
// partial success messages through with the expectation that it doesn't all fall apart.
#[tokio::test(flavor = "multi_thread")]
async fn partial_success_handling() {
use opentelemetry_proto::tonic::collector::trace::v1::ExportTracePartialSuccess;
struct PartialSuccessServer {
tx: Mutex<mpsc::Sender<ExportTraceServiceRequest>>,
}
impl PartialSuccessServer {
pub fn new(tx: mpsc::Sender<ExportTraceServiceRequest>) -> Self {
Self { tx: Mutex::new(tx) }
}
}
#[tonic::async_trait]
impl TraceService for PartialSuccessServer {
async fn export(
&self,
request: tonic::Request<ExportTraceServiceRequest>,
) -> Result<tonic::Response<ExportTraceServiceResponse>, tonic::Status> {
self.tx
.lock()
.unwrap()
.try_send(request.into_inner())
.expect("Channel full");
// Return partial success response
Ok(tonic::Response::new(ExportTraceServiceResponse {
partial_success: Some(ExportTracePartialSuccess {
rejected_spans: 5,
error_message: "Some spans were rejected due to invalid data".to_string(),
}),
}))
}
}
println!("Starting partial success test...");
let addr: SocketAddr = "[::1]:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("failed to bind");
let addr = listener.local_addr().unwrap();
let stream = TcpListenerStream::new(listener).map(|s| {
if let Ok(ref s) = s {
println!("Got new conn at {}", s.peer_addr().unwrap());
}
s
});
let (req_tx, mut req_rx) = mpsc::channel(10);
let service = TraceServiceServer::new(PartialSuccessServer::new(req_tx));
tokio::task::spawn(async move {
tonic::transport::Server::builder()
.add_service(service)
.serve_with_incoming(stream)
.await
.expect("Server failed");
});
{
println!("Installing tracer provider for partial success test...");
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(format!("http://{}", addr))
.build()
.expect("SpanExporter failed to build"),
)
.build();
global::set_tracer_provider(tracer_provider.clone());
let tracer = global::tracer("partial-success-test");
println!("Sending span...");
let mut span = tracer
.span_builder("test-span-partial-success")
.with_kind(SpanKind::Server)
.start(&tracer);
span.end();
// Export should succeed even with partial success response
tracer_provider
.shutdown()
.expect("tracer_provider should shutdown successfully despite partial success");
}
println!("Waiting for request...");
let req = req_rx.recv().await.expect("missing export request");
let first_span = req
.resource_spans
.first()
.unwrap()
.scope_spans
.first()
.unwrap()
.spans
.first()
.unwrap();
assert_eq!("test-span-partial-success", first_span.name);
println!("Partial success test completed successfully");
}