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 + | //! - [`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 + |
|
220 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
221 + | #[test]
|
222 + | #[serial_test::serial]
|
223 + | fn test_detects_otel_provider() {
|
224 + | use aws_smithy_observability_otel::meter::OtelMeterProvider;
|
225 + | use opentelemetry_sdk::metrics::SdkMeterProvider;
|
226 + | use std::sync::Arc;
|
227 + |
|
228 + | let mut context = InterceptorContext::new(Input::doesnt_matter());
|
229 + | context.enter_serialization_phase();
|
230 + | context.set_request(HttpRequest::empty());
|
231 + | let _ = context.take_input();
|
232 + | context.enter_before_transmit_phase();
|
233 + |
|
234 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
235 + | let mut cfg = ConfigBag::base();
|
236 + |
|
237 + | // Create an actual OpenTelemetry meter provider
|
238 + | let otel_sdk_provider = SdkMeterProvider::builder().build();
|
239 + | let otel_provider = Arc::new(OtelMeterProvider::new(otel_sdk_provider));
|
240 + | let telemetry_provider = TelemetryProvider::builder()
|
241 + | .meter_provider(otel_provider)
|
242 + | .build();
|
243 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
244 + |
|
245 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
246 + | let ctx = Into::into(&context);
|
247 + | interceptor
|
248 + | .read_before_signing(&ctx, &rc, &mut cfg)
|
249 + | .unwrap();
|
250 + |
|
251 + | // Should track generic observability metrics for OTel provider
|
252 + | let smithy_features: Vec<_> = cfg
|
253 + | .interceptor_state()
|
254 + | .load::<SmithySdkFeature>()
|
255 + | .cloned()
|
256 + | .collect();
|
257 + | assert!(
|
258 + | smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics),
|
259 + | "Should detect OTel provider as having observability metrics"
|
260 + | );
|
261 + |
|
262 + | // Should ALSO track AWS-specific OTel observability metrics
|
263 + | let aws_features: Vec<_> = cfg
|
264 + | .interceptor_state()
|
265 + | .load::<AwsSdkFeature>()
|
266 + | .cloned()
|
267 + | .collect();
|
268 + | assert!(
|
269 + | aws_features.contains(&AwsSdkFeature::ObservabilityOtelMetrics),
|
270 + | "Should track OTel-specific metrics for OTel provider"
|
271 + | );
|
272 + | }
|
273 + |
|
274 + | // Edge case tests
|
275 + |
|
276 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
277 + | #[test]
|
278 + | #[serial_test::serial]
|
279 + | fn test_multiple_provider_changes() {
|
280 + | use aws_smithy_observability::meter::{Meter, ProvideMeter};
|
281 + | use aws_smithy_observability::noop::NoopMeterProvider;
|
282 + | use aws_smithy_observability::Attributes;
|
283 + | use aws_smithy_observability_otel::meter::OtelMeterProvider;
|
284 + | use opentelemetry_sdk::metrics::SdkMeterProvider;
|
285 + | use std::sync::Arc;
|
286 + |
|
287 + | #[derive(Debug)]
|
288 + | struct CustomMeterProvider {
|
289 + | inner: NoopMeterProvider,
|
290 + | }
|
291 + |
|
292 + | impl ProvideMeter for CustomMeterProvider {
|
293 + | fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter {
|
294 + | self.inner.get_meter(scope, attributes)
|
295 + | }
|
296 + |
|
297 + | fn as_any(&self) -> &dyn std::any::Any {
|
298 + | self
|
299 + | }
|
300 + | }
|
301 + |
|
302 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
303 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
304 + |
|
305 + | // Test 1: Start with noop provider
|
306 + | let _ = aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop());
|
307 + |
|
308 + | let mut context1 = InterceptorContext::new(Input::doesnt_matter());
|
309 + | context1.enter_serialization_phase();
|
310 + | context1.set_request(HttpRequest::empty());
|
311 + | let _ = context1.take_input();
|
312 + | context1.enter_before_transmit_phase();
|
313 + | let mut cfg1 = ConfigBag::base();
|
314 + |
|
315 + | interceptor
|
316 + | .read_before_signing(&Into::into(&context1), &rc, &mut cfg1)
|
317 + | .unwrap();
|
318 + |
|
319 + | let smithy_features: Vec<_> = cfg1
|
320 + | .interceptor_state()
|
321 + | .load::<SmithySdkFeature>()
|
322 + | .cloned()
|
323 + | .collect();
|
324 + | assert_eq!(
|
325 + | smithy_features.len(),
|
326 + | 0,
|
327 + | "Noop provider should not be tracked"
|
328 + | );
|
329 + |
|
330 + | // Test 2: Switch to custom provider
|
331 + | let custom_provider = Arc::new(CustomMeterProvider {
|
332 + | inner: NoopMeterProvider,
|
333 + | });
|
334 + | let telemetry_provider = TelemetryProvider::builder()
|
335 + | .meter_provider(custom_provider)
|
336 + | .build();
|
337 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
338 + |
|
339 + | let mut context2 = InterceptorContext::new(Input::doesnt_matter());
|
340 + | context2.enter_serialization_phase();
|
341 + | context2.set_request(HttpRequest::empty());
|
342 + | let _ = context2.take_input();
|
343 + | context2.enter_before_transmit_phase();
|
344 + | let mut cfg2 = ConfigBag::base();
|
345 + |
|
346 + | interceptor
|
347 + | .read_before_signing(&Into::into(&context2), &rc, &mut cfg2)
|
348 + | .unwrap();
|
349 + |
|
350 + | let smithy_features: Vec<_> = cfg2
|
351 + | .interceptor_state()
|
352 + | .load::<SmithySdkFeature>()
|
353 + | .cloned()
|
354 + | .collect();
|
355 + | assert!(
|
356 + | smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics),
|
357 + | "Custom provider should be tracked"
|
358 + | );
|
359 + | let aws_features: Vec<_> = cfg2
|
360 + | .interceptor_state()
|
361 + | .load::<AwsSdkFeature>()
|
362 + | .cloned()
|
363 + | .collect();
|
364 + | assert_eq!(
|
365 + | aws_features.len(),
|
366 + | 0,
|
367 + | "Custom provider should not have OTel features"
|
368 + | );
|
369 + |
|
370 + | // Test 3: Switch to OTel provider
|
371 + | let otel_sdk_provider = SdkMeterProvider::builder().build();
|
372 + | let otel_provider = Arc::new(OtelMeterProvider::new(otel_sdk_provider));
|
373 + | let telemetry_provider = TelemetryProvider::builder()
|
374 + | .meter_provider(otel_provider)
|
375 + | .build();
|
376 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
377 + |
|
378 + | let mut context3 = InterceptorContext::new(Input::doesnt_matter());
|
379 + | context3.enter_serialization_phase();
|
380 + | context3.set_request(HttpRequest::empty());
|
381 + | let _ = context3.take_input();
|
382 + | context3.enter_before_transmit_phase();
|
383 + | let mut cfg3 = ConfigBag::base();
|
384 + |
|
385 + | interceptor
|
386 + | .read_before_signing(&Into::into(&context3), &rc, &mut cfg3)
|
387 + | .unwrap();
|
388 + |
|
389 + | let smithy_features: Vec<_> = cfg3
|
390 + | .interceptor_state()
|
391 + | .load::<SmithySdkFeature>()
|
392 + | .cloned()
|
393 + | .collect();
|
394 + | assert!(
|
395 + | smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics),
|
396 + | "OTel provider should be tracked"
|
397 + | );
|
398 + | let aws_features: Vec<_> = cfg3
|
399 + | .interceptor_state()
|
400 + | .load::<AwsSdkFeature>()
|
401 + | .cloned()
|
402 + | .collect();
|
403 + | assert!(
|
404 + | aws_features.contains(&AwsSdkFeature::ObservabilityOtelMetrics),
|
405 + | "OTel provider should have OTel features"
|
406 + | );
|
407 + | }
|
408 + |
|
409 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
410 + | #[test]
|
411 + | #[serial_test::serial]
|
412 + | fn test_multiple_invocations_same_provider() {
|
413 + | use aws_smithy_observability::meter::{Meter, ProvideMeter};
|
414 + | use aws_smithy_observability::noop::NoopMeterProvider;
|
415 + | use aws_smithy_observability::Attributes;
|
416 + | use std::sync::Arc;
|
417 + |
|
418 + | #[derive(Debug)]
|
419 + | struct CustomMeterProvider {
|
420 + | inner: NoopMeterProvider,
|
421 + | }
|
422 + |
|
423 + | impl ProvideMeter for CustomMeterProvider {
|
424 + | fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter {
|
425 + | self.inner.get_meter(scope, attributes)
|
426 + | }
|
427 + |
|
428 + | fn as_any(&self) -> &dyn std::any::Any {
|
429 + | self
|
430 + | }
|
431 + | }
|
432 + |
|
433 + | // Set up a custom provider
|
434 + | let custom_provider = Arc::new(CustomMeterProvider {
|
435 + | inner: NoopMeterProvider,
|
436 + | });
|
437 + | let telemetry_provider = TelemetryProvider::builder()
|
438 + | .meter_provider(custom_provider)
|
439 + | .build();
|
440 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
441 + |
|
442 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
443 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
444 + |
|
445 + | // Invoke interceptor multiple times with the same provider
|
446 + | for i in 0..3 {
|
447 + | let mut context = InterceptorContext::new(Input::doesnt_matter());
|
448 + | context.enter_serialization_phase();
|
449 + | context.set_request(HttpRequest::empty());
|
450 + | let _ = context.take_input();
|
451 + | context.enter_before_transmit_phase();
|
452 + | let mut cfg = ConfigBag::base();
|
453 + |
|
454 + | interceptor
|
455 + | .read_before_signing(&Into::into(&context), &rc, &mut cfg)
|
456 + | .unwrap();
|
457 + |
|
458 + | // Each invocation should detect the provider consistently
|
459 + | let smithy_features: Vec<_> = cfg
|
460 + | .interceptor_state()
|
461 + | .load::<SmithySdkFeature>()
|
462 + | .cloned()
|
463 + | .collect();
|
464 + | assert!(
|
465 + | smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics),
|
466 + | "Invocation {} should detect custom provider",
|
467 + | i
|
468 + | );
|
469 + | let aws_features: Vec<_> = cfg
|
470 + | .interceptor_state()
|
471 + | .load::<AwsSdkFeature>()
|
472 + | .cloned()
|
473 + | .collect();
|
474 + | assert_eq!(
|
475 + | aws_features.len(),
|
476 + | 0,
|
477 + | "Invocation {} should not have OTel features for custom provider",
|
478 + | i
|
479 + | );
|
480 + | }
|
481 + | }
|
482 + |
|
483 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
484 + | #[test]
|
485 + | #[serial_test::serial]
|
486 + | fn test_interceptor_handles_errors_gracefully() {
|
487 + | // This test verifies that the interceptor doesn't panic or fail
|
488 + | // when the global provider is in various states
|
489 + |
|
490 + | let interceptor = ObservabilityDetectionInterceptor::new();
|
491 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
492 + |
|
493 + | // Test with noop provider (should succeed without tracking)
|
494 + | let _ = aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop());
|
495 + |
|
496 + | let mut context = InterceptorContext::new(Input::doesnt_matter());
|
497 + | context.enter_serialization_phase();
|
498 + | context.set_request(HttpRequest::empty());
|
499 + | let _ = context.take_input();
|
500 + | context.enter_before_transmit_phase();
|
501 + | let mut cfg = ConfigBag::base();
|
502 + |
|
503 + | // Should not return an error
|
504 + | let result = interceptor.read_before_signing(&Into::into(&context), &rc, &mut cfg);
|
505 + | assert!(
|
506 + | result.is_ok(),
|
507 + | "Interceptor should handle noop provider gracefully"
|
508 + | );
|
509 + |
|
510 + | // Verify no features were tracked
|
511 + | let smithy_features: Vec<_> = cfg
|
512 + | .interceptor_state()
|
513 + | .load::<SmithySdkFeature>()
|
514 + | .cloned()
|
515 + | .collect();
|
516 + | assert_eq!(
|
517 + | smithy_features.len(),
|
518 + | 0,
|
519 + | "Should not track features for noop provider"
|
520 + | );
|
521 + | }
|
522 + |
|
523 + | #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))]
|
524 + | #[test]
|
525 + | #[serial_test::serial]
|
526 + | fn test_interceptor_with_default_constructor() {
|
527 + | use aws_smithy_observability::meter::{Meter, ProvideMeter};
|
528 + | use aws_smithy_observability::noop::NoopMeterProvider;
|
529 + | use aws_smithy_observability::Attributes;
|
530 + | use std::sync::Arc;
|
531 + |
|
532 + | #[derive(Debug)]
|
533 + | struct CustomMeterProvider {
|
534 + | inner: NoopMeterProvider,
|
535 + | }
|
536 + |
|
537 + | impl ProvideMeter for CustomMeterProvider {
|
538 + | fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter {
|
539 + | self.inner.get_meter(scope, attributes)
|
540 + | }
|
541 + |
|
542 + | fn as_any(&self) -> &dyn std::any::Any {
|
543 + | self
|
544 + | }
|
545 + | }
|
546 + |
|
547 + | // Set up a custom provider
|
548 + | let custom_provider = Arc::new(CustomMeterProvider {
|
549 + | inner: NoopMeterProvider,
|
550 + | });
|
551 + | let telemetry_provider = TelemetryProvider::builder()
|
552 + | .meter_provider(custom_provider)
|
553 + | .build();
|
554 + | let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider);
|
555 + |
|
556 + | // Test both constructors produce equivalent behavior
|
557 + | let interceptor_new = ObservabilityDetectionInterceptor::new();
|
558 + | let interceptor_default = ObservabilityDetectionInterceptor::default();
|
559 + |
|
560 + | let rc = RuntimeComponentsBuilder::for_tests().build().unwrap();
|
561 + |
|
562 + | // Test with new()
|
563 + | let mut context1 = InterceptorContext::new(Input::doesnt_matter());
|
564 + | context1.enter_serialization_phase();
|
565 + | context1.set_request(HttpRequest::empty());
|
566 + | let _ = context1.take_input();
|
567 + | context1.enter_before_transmit_phase();
|
568 + | let mut cfg1 = ConfigBag::base();
|
569 + |
|
570 + | interceptor_new
|
571 + | .read_before_signing(&Into::into(&context1), &rc, &mut cfg1)
|
572 + | .unwrap();
|
573 + |
|
574 + | // Test with default()
|
575 + | let mut context2 = InterceptorContext::new(Input::doesnt_matter());
|
576 + | context2.enter_serialization_phase();
|
577 + | context2.set_request(HttpRequest::empty());
|
578 + | let _ = context2.take_input();
|
579 + | context2.enter_before_transmit_phase();
|
580 + | let mut cfg2 = ConfigBag::base();
|
581 + |
|
582 + | interceptor_default
|
583 + | .read_before_signing(&Into::into(&context2), &rc, &mut cfg2)
|
584 + | .unwrap();
|
585 + |
|
586 + | // Both should produce the same results
|
587 + | let smithy_features1: Vec<_> = cfg1
|
588 + | .interceptor_state()
|
589 + | .load::<SmithySdkFeature>()
|
590 + | .cloned()
|
591 + | .collect();
|
592 + | let smithy_features2: Vec<_> = cfg2
|
593 + | .interceptor_state()
|
594 + | .load::<SmithySdkFeature>()
|
595 + | .cloned()
|
596 + | .collect();
|
597 + | assert_eq!(
|
598 + | smithy_features1, smithy_features2,
|
599 + | "Both constructors should produce identical behavior"
|
600 + | );
|
601 + | }
|
602 + | }
|