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 + | use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
|
9 + | use aws_smithy_runtime_api::box_error::BoxError;
|
10 + | use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef;
|
11 + | use aws_smithy_runtime_api::client::interceptors::Intercept;
|
12 + | use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
|
13 + | use aws_smithy_types::config_bag::ConfigBag;
|
14 + |
|
15 + | use 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]
|
21 + | pub struct ObservabilityDetectionInterceptor;
|
22 + |
|
23 + | impl ObservabilityDetectionInterceptor {
|
24 + | /// Creates a new `ObservabilityDetectionInterceptor`
|
25 + | pub fn new() -> Self {
|
26 + | Self
|
27 + | }
|
28 + | }
|
29 + |
|
30 + | impl 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)]
|
77 + | mod 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 + | }
|