@@ -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}
@@ -263,6 +266,34 @@ async fn build_plain_channel(endpoint: &str) -> Result<Channel> {
263266 . wrap_err ( "failed to connect to OpenShell server" )
264267}
265268
269+ /// Marks the current client span failed unless the call finishes successfully.
270+ struct ClientSpanStatus {
271+ span : tracing:: Span ,
272+ finished : bool ,
273+ }
274+
275+ impl ClientSpanStatus {
276+ fn current ( ) -> Self {
277+ Self {
278+ span : tracing:: Span :: current ( ) ,
279+ finished : false ,
280+ }
281+ }
282+
283+ fn finish < T > ( mut self , result : Result < T > ) -> Result < T > {
284+ self . finished = result. is_ok ( ) ;
285+ result
286+ }
287+ }
288+
289+ impl Drop for ClientSpanStatus {
290+ fn drop ( & mut self ) {
291+ if !self . finished {
292+ self . span . record ( "otel.status_code" , "ERROR" ) ;
293+ }
294+ }
295+ }
296+
266297/// Build a Bearer-authenticated channel to the gateway.
267298///
268299/// First call per process resolves the sandbox JWT via the three-step
@@ -901,13 +932,19 @@ pub async fn fetch_policy(
901932/// this snapshot instead of re-fetching metadata after policy construction.
902933/// The snapshot also carries the external middleware registrations required
903934/// by the policy.
935+ #[ tracing:: instrument(
936+ name = "supervisor.gateway.fetch_settings_snapshot" ,
937+ skip_all,
938+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
939+ ) ]
904940pub async fn fetch_settings_snapshot (
905941 endpoint : & str ,
906942 sandbox_name : & str ,
907943) -> Result < SettingsPollResult > {
944+ let status = ClientSpanStatus :: current ( ) ;
908945 debug ! ( endpoint = %endpoint, sandbox_name = %sandbox_name, "Connecting to fetch OpenShell settings snapshot" ) ;
909946 let mut client = connect ( endpoint) . await ?;
910- fetch_settings_snapshot_with_client ( & mut client, sandbox_name, None ) . await
947+ status . finish ( fetch_settings_snapshot_with_client ( & mut client, sandbox_name, None ) . await )
911948}
912949
913950async fn fetch_settings_snapshot_with_client (
@@ -1014,19 +1051,30 @@ pub async fn sync_policy(
10141051}
10151052
10161053/// Sync an enriched policy and return the authoritative revision snapshot.
1054+ #[ tracing:: instrument(
1055+ name = "supervisor.gateway.sync_policy_and_fetch_snapshot" ,
1056+ skip_all,
1057+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1058+ ) ]
10171059pub async fn sync_policy_and_fetch_snapshot (
10181060 endpoint : & str ,
10191061 sandbox : & str ,
10201062 policy : & ProtoSandboxPolicy ,
10211063 workspace : & str ,
10221064) -> Result < SettingsPollResult > {
1065+ let status = ClientSpanStatus :: current ( ) ;
10231066 let mut client = connect ( endpoint) . await ?;
10241067 sync_policy_with_client ( & mut client, sandbox, policy, workspace) . await ?;
1025- fetch_settings_snapshot_with_client ( & mut client, sandbox, Some ( workspace) ) . await
1068+ status . finish ( fetch_settings_snapshot_with_client ( & mut client, sandbox, Some ( workspace) ) . await )
10261069}
10271070
10281071/// Report an exact runtime configuration generation. Pending registration uses
10291072/// the snapshot's instance fence; retain that snapshot across registration retries.
1073+ #[ tracing:: instrument(
1074+ name = "supervisor.gateway.report_sandbox_configuration" ,
1075+ skip_all,
1076+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1077+ ) ]
10301078pub async fn report_sandbox_configuration (
10311079 endpoint : & str ,
10321080 sandbox_id : & str ,
@@ -1035,6 +1083,7 @@ pub async fn report_sandbox_configuration(
10351083 state : crate :: proto:: ConfigurationAdmissionState ,
10361084 error : & str ,
10371085) -> Result < ( ) > {
1086+ let status = ClientSpanStatus :: current ( ) ;
10381087 let mut client = connect ( endpoint) . await ?;
10391088 client
10401089 . report_sandbox_configuration ( crate :: proto:: ReportSandboxConfigurationRequest {
@@ -1056,18 +1105,24 @@ pub async fn report_sandbox_configuration(
10561105 } )
10571106 . await
10581107 . map_err ( grpc_status_error) ?;
1059- Ok ( ( ) )
1108+ status . finish ( Ok ( ( ) ) )
10601109}
10611110
10621111/// Fetch provider environment variables for a sandbox from `OpenShell` server via gRPC.
10631112///
10641113/// Returns the credential snapshot and its exact readiness identity. An empty
10651114/// environment represents a sandbox without provider credentials. Transport
10661115/// failure returns an error so callers can revoke credentials and retry.
1116+ #[ tracing:: instrument(
1117+ name = "supervisor.gateway.fetch_provider_environment" ,
1118+ skip_all,
1119+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1120+ ) ]
10671121pub async fn fetch_provider_environment (
10681122 endpoint : & str ,
10691123 sandbox_id : & str ,
10701124) -> Result < ProviderEnvironmentResult > {
1125+ let status = ClientSpanStatus :: current ( ) ;
10711126 debug ! ( endpoint = %endpoint, sandbox_id = %sandbox_id, "Fetching provider environment" ) ;
10721127
10731128 let mut client = connect ( endpoint) . await ?;
@@ -1080,7 +1135,7 @@ pub async fn fetch_provider_environment(
10801135 . await
10811136 . map_err ( grpc_status_error) ?;
10821137
1083- provider_environment_result ( response. into_inner ( ) )
1138+ status . finish ( provider_environment_result ( response. into_inner ( ) ) )
10841139}
10851140
10861141/// Preserve snapshot authority and reject invalid credential expiration times.
@@ -1181,13 +1236,19 @@ mod provider_environment_tests {
11811236 }
11821237}
11831238
1239+ #[ tracing:: instrument(
1240+ name = "supervisor.gateway.exchange_provider_subject_token" ,
1241+ skip_all,
1242+ fields( otel. kind = "client" , otel. status_code = tracing:: field:: Empty )
1243+ ) ]
11841244pub async fn exchange_provider_subject_token (
11851245 endpoint : & str ,
11861246 sandbox_id : & str ,
11871247 provider : & str ,
11881248 credential_key : & str ,
11891249 supervisor_jwt_svid : & str ,
11901250) -> Result < ProviderSubjectTokenExchangeResult > {
1251+ let status = ClientSpanStatus :: current ( ) ;
11911252 debug ! (
11921253 endpoint = %endpoint,
11931254 sandbox_id = %sandbox_id,
@@ -1216,11 +1277,11 @@ pub async fn exchange_provider_subject_token(
12161277 . map_or ( 0 , |value| {
12171278 i64:: try_from ( value. as_secs ( ) ) . unwrap_or ( i64:: MAX )
12181279 } ) ;
1219- Ok ( ProviderSubjectTokenExchangeResult {
1280+ status . finish ( Ok ( ProviderSubjectTokenExchangeResult {
12201281 access_token : inner. access_token ,
12211282 expires_in,
12221283 token_type : inner. token_type ,
1223- } )
1284+ } ) )
12241285}
12251286
12261287fn provider_subject_token_exchange_status ( status : Status ) -> miette:: Report {
0 commit comments