@@ -16,7 +16,8 @@ use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Chann
16
16
17
17
use super :: BoxInterceptor ;
18
18
19
- use opentelemetry_sdk:: retry:: { retry_with_exponential_backoff, RetryPolicy } ;
19
+ use crate :: retry_classification:: grpc:: classify_tonic_status;
20
+ use opentelemetry_sdk:: retry:: { RetryPolicy , retry_with_exponential_backoff_classified} ;
20
21
use opentelemetry_sdk:: runtime:: Tokio ;
21
22
22
23
pub ( crate ) struct TonicTracesClient {
@@ -73,54 +74,52 @@ impl SpanExporter for TonicTracesClient {
73
74
74
75
let batch = Arc :: new ( batch) ;
75
76
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 } ,
106
- ) )
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
- }
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)
119
99
}
120
- } )
100
+ None => return Err ( tonic:: Status :: failed_precondition ( "exporter already shutdown" ) ) ,
101
+ } ;
102
+
103
+ let resource_spans =
104
+ group_spans_by_resource_and_scope ( ( * batch_clone) . clone ( ) , & self . resource ) ;
105
+
106
+ otel_debug ! ( name: "TonicTracesClient.ExportStarted" ) ;
107
+
108
+ client
109
+ . export ( Request :: from_parts (
110
+ metadata,
111
+ extensions,
112
+ ExportTraceServiceRequest { resource_spans } ,
113
+ ) )
114
+ . await
115
+ . map ( |_| {
116
+ otel_debug ! ( name: "TonicTracesClient.ExportSucceeded" ) ;
117
+ } )
121
118
}
122
- } )
123
- . await
119
+ ) . await {
120
+ Ok ( _) => Ok ( ( ) ) ,
121
+ Err ( tonic_status) => Err ( OTelSdkError :: InternalFailure ( format ! ( "export error: {tonic_status:?}" ) ) ) ,
122
+ }
124
123
}
125
124
126
125
fn shutdown ( & mut self ) -> OTelSdkResult {
0 commit comments