@@ -195,6 +195,9 @@ impl tonic::service::Interceptor for AuthInterceptor {
195195 . expect ( "auth interceptor token slot poisoned" )
196196 . clone ( ) ;
197197 req. metadata_mut ( ) . insert ( "authorization" , bearer) ;
198+ #[ cfg( feature = "trace-context" ) ]
199+ let req =
200+ tonic:: service:: Interceptor :: call ( & mut openshell_otel:: TraceContextInterceptor , req) ?;
198201 Ok ( req)
199202 }
200203}
@@ -280,6 +283,34 @@ async fn build_plain_channel(endpoint: &str) -> Result<Channel> {
280283 . wrap_err ( "failed to connect to OpenShell server" )
281284}
282285
286+ /// Marks the current client span failed unless the call finishes successfully.
287+ struct ClientSpanStatus {
288+ span : tracing:: Span ,
289+ finished : bool ,
290+ }
291+
292+ impl ClientSpanStatus {
293+ fn current ( ) -> Self {
294+ Self {
295+ span : tracing:: Span :: current ( ) ,
296+ finished : false ,
297+ }
298+ }
299+
300+ fn finish < T > ( mut self , result : Result < T > ) -> Result < T > {
301+ self . finished = result. is_ok ( ) ;
302+ result
303+ }
304+ }
305+
306+ impl Drop for ClientSpanStatus {
307+ fn drop ( & mut self ) {
308+ if !self . finished {
309+ self . span . record ( "otel.status_code" , "ERROR" ) ;
310+ }
311+ }
312+ }
313+
283314/// Build a Bearer-authenticated channel to the gateway.
284315///
285316/// First call per process resolves the sandbox JWT via the three-step
@@ -918,13 +949,19 @@ pub async fn fetch_policy(
918949/// this snapshot instead of re-fetching metadata after policy construction.
919950/// The snapshot also carries the external middleware registrations required
920951/// by the policy.
952+ #[ tracing:: instrument(
953+ name = "supervisor.gateway.fetch_settings_snapshot" ,
954+ skip_all,
955+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
956+ ) ]
921957pub async fn fetch_settings_snapshot (
922958 endpoint : & str ,
923959 sandbox_name : & str ,
924960) -> Result < SettingsPollResult > {
961+ let status = ClientSpanStatus :: current ( ) ;
925962 debug ! ( endpoint = %endpoint, sandbox_name = %sandbox_name, "Connecting to fetch OpenShell settings snapshot" ) ;
926963 let mut client = connect ( endpoint) . await ?;
927- fetch_settings_snapshot_with_client ( & mut client, sandbox_name, None ) . await
964+ status . finish ( fetch_settings_snapshot_with_client ( & mut client, sandbox_name, None ) . await )
928965}
929966
930967async fn fetch_settings_snapshot_with_client (
@@ -1031,19 +1068,30 @@ pub async fn sync_policy(
10311068}
10321069
10331070/// Sync an enriched policy and return the authoritative revision snapshot.
1071+ #[ tracing:: instrument(
1072+ name = "supervisor.gateway.sync_policy_and_fetch_snapshot" ,
1073+ skip_all,
1074+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1075+ ) ]
10341076pub async fn sync_policy_and_fetch_snapshot (
10351077 endpoint : & str ,
10361078 sandbox : & str ,
10371079 policy : & ProtoSandboxPolicy ,
10381080 workspace : & str ,
10391081) -> Result < SettingsPollResult > {
1082+ let status = ClientSpanStatus :: current ( ) ;
10401083 let mut client = connect ( endpoint) . await ?;
10411084 sync_policy_with_client ( & mut client, sandbox, policy, workspace) . await ?;
1042- fetch_settings_snapshot_with_client ( & mut client, sandbox, Some ( workspace) ) . await
1085+ status . finish ( fetch_settings_snapshot_with_client ( & mut client, sandbox, Some ( workspace) ) . await )
10431086}
10441087
10451088/// Report an exact runtime configuration generation. Pending registration uses
10461089/// the snapshot's instance fence; retain that snapshot across registration retries.
1090+ #[ tracing:: instrument(
1091+ name = "supervisor.gateway.report_sandbox_configuration" ,
1092+ skip_all,
1093+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1094+ ) ]
10471095pub async fn report_sandbox_configuration (
10481096 endpoint : & str ,
10491097 sandbox_id : & str ,
@@ -1052,6 +1100,7 @@ pub async fn report_sandbox_configuration(
10521100 state : crate :: proto:: ConfigurationAdmissionState ,
10531101 error : & str ,
10541102) -> Result < ( ) > {
1103+ let status = ClientSpanStatus :: current ( ) ;
10551104 let mut client = connect ( endpoint) . await ?;
10561105 client
10571106 . report_sandbox_configuration ( crate :: proto:: ReportSandboxConfigurationRequest {
@@ -1073,18 +1122,24 @@ pub async fn report_sandbox_configuration(
10731122 } )
10741123 . await
10751124 . map_err ( grpc_status_error) ?;
1076- Ok ( ( ) )
1125+ status . finish ( Ok ( ( ) ) )
10771126}
10781127
10791128/// Fetch provider environment variables for a sandbox from `OpenShell` server via gRPC.
10801129///
10811130/// Returns the credential snapshot and its exact readiness identity. An empty
10821131/// environment represents a sandbox without provider credentials. Transport
10831132/// failure returns an error so callers can revoke credentials and retry.
1133+ #[ tracing:: instrument(
1134+ name = "supervisor.gateway.fetch_provider_environment" ,
1135+ skip_all,
1136+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1137+ ) ]
10841138pub async fn fetch_provider_environment (
10851139 endpoint : & str ,
10861140 sandbox_id : & str ,
10871141) -> Result < ProviderEnvironmentResult > {
1142+ let status = ClientSpanStatus :: current ( ) ;
10881143 debug ! ( endpoint = %endpoint, sandbox_id = %sandbox_id, "Fetching provider environment" ) ;
10891144
10901145 let mut client = connect ( endpoint) . await ?;
@@ -1097,7 +1152,7 @@ pub async fn fetch_provider_environment(
10971152 . await
10981153 . map_err ( grpc_status_error) ?;
10991154
1100- provider_environment_result ( response. into_inner ( ) )
1155+ status . finish ( provider_environment_result ( response. into_inner ( ) ) )
11011156}
11021157
11031158/// Preserve snapshot authority and reject invalid credential expiration times.
@@ -1198,13 +1253,19 @@ mod provider_environment_tests {
11981253 }
11991254}
12001255
1256+ #[ tracing:: instrument(
1257+ name = "supervisor.gateway.exchange_provider_subject_token" ,
1258+ skip_all,
1259+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1260+ ) ]
12011261pub async fn exchange_provider_subject_token (
12021262 endpoint : & str ,
12031263 sandbox_id : & str ,
12041264 provider : & str ,
12051265 credential_key : & str ,
12061266 supervisor_jwt_svid : & str ,
12071267) -> Result < ProviderSubjectTokenExchangeResult > {
1268+ let status = ClientSpanStatus :: current ( ) ;
12081269 debug ! (
12091270 endpoint = %endpoint,
12101271 sandbox_id = %sandbox_id,
@@ -1233,11 +1294,11 @@ pub async fn exchange_provider_subject_token(
12331294 . map_or ( 0 , |value| {
12341295 i64:: try_from ( value. as_secs ( ) ) . unwrap_or ( i64:: MAX )
12351296 } ) ;
1236- Ok ( ProviderSubjectTokenExchangeResult {
1297+ status . finish ( Ok ( ProviderSubjectTokenExchangeResult {
12371298 access_token : inner. access_token ,
12381299 expires_in,
12391300 token_type : inner. token_type ,
1240- } )
1301+ } ) )
12411302}
12421303
12431304fn provider_subject_token_exchange_status ( status : Status ) -> miette:: Report {
0 commit comments