aws_runtime/
observability_detection.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Observability feature detection for business metrics tracking
7
8use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
9use aws_smithy_runtime_api::box_error::BoxError;
10use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef;
11use aws_smithy_runtime_api::client::interceptors::Intercept;
12use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
13use aws_smithy_types::config_bag::ConfigBag;
14
15use crate::sdk_feature::AwsSdkFeature;
16
17/// Interceptor that detects when observability features are being used
18/// and tracks them for business metrics.
19#[derive(Debug, Default)]
20#[non_exhaustive]
21pub struct ObservabilityDetectionInterceptor;
22
23impl ObservabilityDetectionInterceptor {
24    /// Creates a new `ObservabilityDetectionInterceptor`
25    pub fn new() -> Self {
26        Self
27    }
28}
29
30impl Intercept for ObservabilityDetectionInterceptor {
31    fn name(&self) -> &'static str {
32        "ObservabilityDetectionInterceptor"
33    }
34
35    fn read_after_serialization(
36        &self,
37        _context: &BeforeTransmitInterceptorContextRef<'_>,
38        _runtime_components: &RuntimeComponents,
39        cfg: &mut ConfigBag,
40    ) -> Result<(), BoxError> {
41        // Try to get the global telemetry provider
42        if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() {
43            // Check if it's not a noop provider
44            if !provider.is_noop() {
45                // Track that observability metrics are enabled
46                cfg.interceptor_state()
47                    .store_append(AwsSdkFeature::ObservabilityMetrics);
48
49                // PRAGMATIC APPROACH: Track tracing based on meter provider state
50                //
51                // The SDK uses Rust's `tracing` crate globally but doesn't have a
52                // configurable tracer provider yet. We make the reasonable assumption
53                // that if a user has configured a meter provider, they've also set up
54                // tracing as part of their observability stack.
55                //
56                // This is a pragmatic workaround until a proper tracer provider is added.
57                cfg.interceptor_state()
58                    .store_append(SmithySdkFeature::ObservabilityTracing);
59
60                // Check if it's using OpenTelemetry
61                if provider.is_otel() {
62                    cfg.interceptor_state()
63                        .store_append(AwsSdkFeature::ObservabilityOtelMetrics);
64
65                    // If using OpenTelemetry for metrics, likely using it for tracing too
66                    cfg.interceptor_state()
67                        .store_append(AwsSdkFeature::ObservabilityOtelTracing);
68                }
69            }
70        }
71
72        Ok(())
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use aws_smithy_observability::TelemetryProvider;
80    use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext};
81    use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
82    use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
83    use aws_smithy_types::config_bag::ConfigBag;
84
85    #[test]
86    fn test_detects_noop_provider() {
87        let mut context = InterceptorContext::new(Input::doesnt_matter());
88        context.enter_serialization_phase();
89        context.set_request(HttpRequest::empty());
90        let _ = context.take_input();
91        context.enter_before_transmit_phase();
92
93        let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
94        let mut cfg = ConfigBag::base();
95
96        // Set a noop provider
97        aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
98            .unwrap();
99
100        let interceptor = ObservabilityDetectionInterceptor::new();
101        let ctx = Into::into(&context);
102        interceptor
103            .read_after_serialization(&ctx, &rc, &mut cfg)
104            .unwrap();
105
106        // Should not track any features for noop provider
107        let features: Vec<_> = cfg.load::<AwsSdkFeature>().collect();
108        assert_eq!(features.len(), 0);
109    }
110
111    // Note: We cannot easily test non-noop providers without creating a custom meter provider
112    // implementation, which would require exposing internal types. The noop test above
113    // is sufficient to verify the detection logic works correctly.
114}