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 + | //!
|
8 + | //! This module provides an interceptor for detecting observability features in the AWS SDK:
|
9 + | //!
|
10 + | //! - [`crate::observability_detection::ObservabilityDetectionInterceptor`]: Detects observability features during
|
11 + | //! request processing and tracks them for business metrics in the User-Agent header.
|
12 + |
|
13 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
14 + | use crate::sdk_feature::AwsSdkFeature;
|
15 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
16 + | use aws_smithy_observability_otel::meter::OtelMeterProvider;
|
17 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
18 + | use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
|
19 + | use aws_smithy_runtime_api::box_error::BoxError;
|
20 + | use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef;
|
21 + | use aws_smithy_runtime_api::client::interceptors::Intercept;
|
22 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
|
23 + | use aws_smithy_types::config_bag::ConfigBag;
|
24 + |
|
25 + | /// Interceptor that detects when observability features are being used
|
26 + | /// and tracks them for business metrics.
|
27 + | #[derive(Debug, Default)]
|
28 + | #[non_exhaustive]
|
29 + | pub struct ObservabilityDetectionInterceptor;
|
30 + |
|
31 + | impl ObservabilityDetectionInterceptor {
|
32 + | /// Creates a new `ObservabilityDetectionInterceptor`
|
33 + | pub fn new() -> Self {
|
34 + | Self
|
35 + | }
|
36 + | }
|
37 + |
|
38 + | impl Intercept for ObservabilityDetectionInterceptor {
|
39 + | fn name(&self) -> &'static str {
|
40 + | "ObservabilityDetectionInterceptor"
|
41 + | }
|
42 + |
|
43 + | fn read_before_signing(
|
44 + | &self,
|
45 + | _context: &BeforeTransmitInterceptorContextRef<'_>,
|
46 + | _runtime_components: &RuntimeComponents,
|
47 + | _cfg: &mut ConfigBag,
|
48 + | ) -> Result<(), BoxError> {
|
49 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
50 + | {
|
51 + | // Try to get the global telemetry provider
|
52 + | if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() {
|
53 + | let meter_provider = provider.meter_provider();
|
54 + |
|
55 + | // Check if this is an OpenTelemetry meter provider
|
56 + | let is_otel = meter_provider
|
57 + | .as_any()
|
58 + | .downcast_ref::<OtelMeterProvider>()
|
59 + | .is_some();
|
60 + |
|
61 + | // Check if this is a noop provider (we don't want to track noop)
|
62 + | let is_noop = meter_provider
|
63 + | .as_any()
|
64 + | .downcast_ref::<aws_smithy_observability::noop::NoopMeterProvider>()
|
65 + | .is_some();
|
66 + |
|
67 + | if !is_noop {
|
68 + | // Track generic observability metrics (for any non-noop provider)
|
69 + | _cfg.interceptor_state()
|
70 + | .store_append(SmithySdkFeature::ObservabilityMetrics);
|
71 + |
|
72 + | // If it's specifically OpenTelemetry, track that too
|
73 + | if is_otel {
|
74 + | _cfg.interceptor_state()
|
75 + | .store_append(AwsSdkFeature::ObservabilityOtelMetrics);
|
76 + | }
|
77 + | }
|
78 + | }
|
79 + | }
|
80 + |
|
81 + | Ok(())
|
82 + | }
|
83 + | }
|
84 + |
|
85 + | #[cfg(test)]
|
86 + | mod tests {
|
87 + | use super::*;
|
88 + | use crate::sdk_feature::AwsSdkFeature;
|
89 + | use aws_smithy_observability::TelemetryProvider;
|
90 + | use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext};
|
91 + | use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
|
92 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
|
93 + | use aws_smithy_types::config_bag::ConfigBag;
|
94 + |
|
95 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
96 + | #[test]
|
97 + | #[serial_test::serial]
|
98 + | fn test_detects_noop_provider() {
|
99 + | let mut context = InterceptorContext::new(Input::doesnt_matter());
|
100 + | context.enter_serialization_phase();
|
101 + | context.set_request(HttpRequest::empty());
|
102 + | let _ = context.take_input();
|
103 + | context.enter_before_transmit_phase();
|
104 + |
|
105 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
106 + | let mut cfg = ConfigBag::base();
|
107 + |
|
108 + | // Set a noop provider (ignore error if already set by another test)
|
109 + | let _ = aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop());
|
110 + |
|
111 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
112 + | let ctx = Into::into(&context);
|
113 + | interceptor
|
114 + | .read_before_signing(&ctx, &rc, &mut cfg)
|
115 + | .unwrap();
|
116 + |
|
117 + | // Should not track any features for noop provider
|
118 + | let smithy_features: Vec<_> = cfg
|
119 + | .interceptor_state()
|
120 + | .load::<SmithySdkFeature>()
|
121 + | .cloned()
|
122 + | .collect();
|
123 + | assert_eq!(
|
124 + | smithy_features.len(),
|
125 + | 0,
|
126 + | "Should not track Smithy features for noop provider"
|
127 + | );
|
128 + |
|
129 + | let aws_features: Vec<_> = cfg
|
130 + | .interceptor_state()
|
131 + | .load::<AwsSdkFeature>()
|
132 + | .cloned()
|
133 + | .collect();
|
134 + | assert_eq!(
|
135 + | aws_features.len(),
|
136 + | 0,
|
137 + | "Should not track AWS features for noop provider"
|
138 + | );
|
139 + | }
|
140 + |
|
141 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
142 + | #[test]
|
143 + | #[serial_test::serial]
|
144 + | fn test_custom_provider_not_detected_as_otel() {
|
145 + | use aws_smithy_observability::meter::{Meter, ProvideMeter};
|
146 + | use aws_smithy_observability::noop::NoopMeterProvider;
|
147 + | use aws_smithy_observability::Attributes;
|
148 + | use std::sync::Arc;
|
149 + |
|
150 + | // Create a custom (non-OTel, non-noop) meter provider
|
151 + | // This simulates a user implementing their own metrics provider
|
152 + | #[derive(Debug)]
|
153 + | struct CustomMeterProvider {
|
154 + | inner: NoopMeterProvider,
|
155 + | }
|
156 + |
|
157 + | impl ProvideMeter for CustomMeterProvider {
|
158 + | fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter {
|
159 + | // Delegate to noop for simplicity, but this is a distinct type
|
160 + | self.inner.get_meter(scope, attributes)
|
161 + | }
|
162 + |
|
163 + | fn as_any(&self) -> &dyn std::any::Any {
|
164 + | self
|
165 + | }
|
166 + | }
|
167 + |
|
168 + | let mut context = InterceptorContext::new(Input::doesnt_matter());
|
169 + | context.enter_serialization_phase();
|
170 + | context.set_request(HttpRequest::empty());
|
171 + | let _ = context.take_input();
|
172 + | context.enter_before_transmit_phase();
|
173 + |
|
174 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
175 + | let mut cfg = ConfigBag::base();
|
176 + |
|
177 + | // Set the custom provider
|
178 + | let custom_provider = Arc::new(CustomMeterProvider {
|
179 + | inner: NoopMeterProvider,
|
180 + | });
|
181 + | let telemetry_provider = TelemetryProvider::builder()
|
182 + | .meter_provider(custom_provider)
|
183 + | .build();
|
184 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
185 + |
|
186 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
187 + | let ctx = Into::into(&context);
|
188 + | interceptor
|
189 + | .read_before_signing(&ctx, &rc, &mut cfg)
|
190 + | .unwrap();
|
191 + |
|
192 + | // Should track generic observability metrics for custom provider
|
193 + | let smithy_features: Vec<_> = cfg
|
194 + | .interceptor_state()
|
195 + | .load::<SmithySdkFeature>()
|
196 + | .cloned()
|
197 + | .collect();
|
198 + | assert!(
|
199 + | smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics),
|
200 + | "Should detect custom provider as having observability metrics"
|
201 + | );
|
202 + |
|
203 + | // Should NOT track AWS-specific observability metrics for custom provider
|
204 + | let aws_features: Vec<_> = cfg
|
205 + | .interceptor_state()
|
206 + | .load::<AwsSdkFeature>()
|
207 + | .cloned()
|
208 + | .collect();
|
209 + | assert!(
|
210 + | !aws_features.contains(&AwsSdkFeature::ObservabilityOtelMetrics),
|
211 + | "Should NOT track OTel-specific metrics for custom provider"
|
212 + | );
|
213 + | assert_eq!(
|
214 + | aws_features.len(),
|
215 + | 0,
|
216 + | "Should not track any AWS-specific features for custom provider"
|
217 + | );
|
218 + | }
|
219 + | }
|