@@ -16,7 +16,8 @@ use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Chann
1616
1717use super :: BoxInterceptor ;
1818
19- use opentelemetry_sdk:: retry:: { retry_with_exponential_backoff, RetryPolicy } ;
19+ use crate :: retry_classification:: grpc:: classify_tonic_status;
20+ use opentelemetry_sdk:: retry:: { retry_with_exponential_backoff_classified, RetryPolicy } ;
2021use opentelemetry_sdk:: runtime:: Tokio ;
2122
2223pub ( crate ) struct TonicTracesClient {
@@ -73,54 +74,60 @@ impl SpanExporter for TonicTracesClient {
7374
7475 let batch = Arc :: new ( batch) ;
7576
76- retry_with_exponential_backoff ( Tokio , policy, "TonicTracesClient.Export" , {
77- let batch = Arc :: clone ( & batch) ;
78- let inner = & self . inner ;
79- let resource = & self . resource ;
80- move || {
81- let batch = Arc :: clone ( & batch) ;
82- Box :: pin ( async move {
83- let ( mut client, metadata, extensions) = match inner {
84- Some ( inner) => {
85- let ( m, e, _) = inner
86- . interceptor
87- . lock ( )
88- . await // tokio::sync::Mutex doesn't return a poisoned error, so we can safely use the interceptor here
89- . call ( Request :: new ( ( ) ) )
90- . map_err ( |e| OTelSdkError :: InternalFailure ( format ! ( "error: {e:?}" ) ) ) ?
91- . into_parts ( ) ;
92- ( inner. client . clone ( ) , m, e)
93- }
94- None => return Err ( OTelSdkError :: AlreadyShutdown ) ,
95- } ;
96-
97- let resource_spans = group_spans_by_resource_and_scope ( ( * batch) . clone ( ) , resource) ;
98-
99- otel_debug ! ( name: "TonicTracesClient.ExportStarted" ) ;
100-
101- let result = client
102- . export ( Request :: from_parts (
103- metadata,
104- extensions,
105- ExportTraceServiceRequest { resource_spans } ,
77+ match retry_with_exponential_backoff_classified (
78+ Tokio ,
79+ policy,
80+ classify_tonic_status,
81+ "TonicTracesClient.Export" ,
82+ || async {
83+ let batch_clone = Arc :: clone ( & batch) ;
84+
85+ // Execute the export operation
86+ let ( mut client, metadata, extensions) = match & self . inner {
87+ Some ( inner) => {
88+ let ( m, e, _) = inner
89+ . interceptor
90+ . lock ( )
91+ . await // tokio::sync::Mutex doesn't return a poisoned error, so we can safely use the interceptor here
92+ . call ( Request :: new ( ( ) ) )
93+ . map_err ( |e| {
94+ // Convert interceptor errors to tonic::Status for retry classification
95+ tonic:: Status :: internal ( format ! ( "interceptor error: {e:?}" ) )
96+ } ) ?
97+ . into_parts ( ) ;
98+ ( inner. client . clone ( ) , m, e)
99+ }
100+ None => {
101+ return Err ( tonic:: Status :: failed_precondition (
102+ "exporter already shutdown" ,
106103 ) )
107- . await ;
108-
109- match result {
110- Ok ( _) => {
111- otel_debug ! ( name: "TonicTracesClient.ExportSucceeded" ) ;
112- Ok ( ( ) )
113- }
114- Err ( e) => {
115- let error = format ! ( "export error: {e:?}" ) ;
116- otel_debug ! ( name: "TonicTracesClient.ExportFailed" , error = & error) ;
117- Err ( OTelSdkError :: InternalFailure ( error) )
118- }
119104 }
120- } )
121- }
122- } )
105+ } ;
106+
107+ let resource_spans =
108+ group_spans_by_resource_and_scope ( ( * batch_clone) . clone ( ) , & self . resource ) ;
109+
110+ otel_debug ! ( name: "TonicTracesClient.ExportStarted" ) ;
111+
112+ client
113+ . export ( Request :: from_parts (
114+ metadata,
115+ extensions,
116+ ExportTraceServiceRequest { resource_spans } ,
117+ ) )
118+ . await
119+ . map ( |_| {
120+ otel_debug ! ( name: "TonicTracesClient.ExportSucceeded" ) ;
121+ } )
122+ } ,
123+ )
123124 . await
125+ {
126+ Ok ( _) => Ok ( ( ) ) ,
127+ Err ( tonic_status) => Err ( OTelSdkError :: InternalFailure ( format ! (
128+ "export error: {tonic_status:?}"
129+ ) ) ) ,
130+ }
124131 }
125132
126133 fn shutdown ( & mut self ) -> OTelSdkResult {
0 commit comments