1 + | /*
|
2 + | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 + | * SPDX-License-Identifier: Apache-2.0
|
4 + | */
|
5 + |
|
6 + | use aws_config::Region;
|
7 + | use aws_credential_types::Credentials;
|
8 + | use aws_runtime::user_agent::test_util::assert_ua_contains_metric_values;
|
9 + | use aws_sdk_dynamodb::config::Builder;
|
10 + | use aws_smithy_observability::TelemetryProvider;
|
11 + | use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient};
|
12 + | use aws_smithy_types::body::SdkBody;
|
13 + | use std::sync::Arc;
|
14 + |
|
15 + | fn test_client(
|
16 + | update_builder: fn(Builder) -> Builder,
|
17 + | ) -> (aws_sdk_dynamodb::Client, StaticReplayClient) {
|
18 + | let http_client = StaticReplayClient::new(vec![ReplayEvent::new(
|
19 + | http::Request::builder()
|
20 + | .uri("https://dynamodb.us-east-1.amazonaws.com/")
|
21 + | .body(SdkBody::empty())
|
22 + | .unwrap(),
|
23 + | http::Response::builder()
|
24 + | .status(200)
|
25 + | .body(SdkBody::from(r#"{"TableNames":[]}"#))
|
26 + | .unwrap(),
|
27 + | )]);
|
28 + |
|
29 + | let config = update_builder(
|
30 + | aws_sdk_dynamodb::Config::builder()
|
31 + | .credentials_provider(Credentials::for_tests())
|
32 + | .region(Region::from_static("us-east-1"))
|
33 + | .http_client(http_client.clone())
|
34 + | .behavior_version_latest(),
|
35 + | )
|
36 + | .build();
|
37 + |
|
38 + | (aws_sdk_dynamodb::Client::from_conf(config), http_client)
|
39 + | }
|
40 + |
|
41 + | async fn call_operation(client: aws_sdk_dynamodb::Client) {
|
42 + | let _ = client.list_tables().send().await;
|
43 + | }
|
44 + |
|
45 + | // Mock OTel meter provider for testing
|
46 + | // This mimics the real OtelMeterProvider's type name without requiring the broken opentelemetry crate
|
47 + | // We create it in a module path that matches what our type checking looks for
|
48 + | mod mock_otel {
|
49 + | use aws_smithy_observability::meter::{Meter, ProvideMeter};
|
50 + | use aws_smithy_observability::Attributes;
|
51 + |
|
52 + | #[derive(Debug)]
|
53 + | pub struct OtelMeterProvider;
|
54 + |
|
55 + | impl ProvideMeter for OtelMeterProvider {
|
56 + | fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter {
|
57 + | // Return a noop meter - we don't actually need it to work, just to exist
|
58 + | Meter::new(std::sync::Arc::new(NoopInstrumentProvider))
|
59 + | }
|
60 + |
|
61 + | fn provider_name(&self) -> &'static str {
|
62 + | "otel"
|
63 + | }
|
64 + |
|
65 + | fn as_any(&self) -> &dyn std::any::Any {
|
66 + | self
|
67 + | }
|
68 + | }
|
69 + |
|
70 + | #[derive(Debug)]
|
71 + | struct NoopInstrumentProvider;
|
72 + |
|
73 + | impl aws_smithy_observability::instruments::ProvideInstrument for NoopInstrumentProvider {
|
74 + | fn create_gauge(
|
75 + | &self,
|
76 + | _builder: aws_smithy_observability::instruments::AsyncInstrumentBuilder<
|
77 + | '_,
|
78 + | std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = f64>>,
|
79 + | f64,
|
80 + | >,
|
81 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = f64>>
|
82 + | {
|
83 + | std::sync::Arc::new(NoopAsync::<f64>(std::marker::PhantomData))
|
84 + | }
|
85 + |
|
86 + | fn create_up_down_counter(
|
87 + | &self,
|
88 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
89 + | '_,
|
90 + | std::sync::Arc<dyn aws_smithy_observability::instruments::UpDownCounter>,
|
91 + | >,
|
92 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::UpDownCounter> {
|
93 + | std::sync::Arc::new(NoopUpDown)
|
94 + | }
|
95 + |
|
96 + | fn create_async_up_down_counter(
|
97 + | &self,
|
98 + | _builder: aws_smithy_observability::instruments::AsyncInstrumentBuilder<
|
99 + | '_,
|
100 + | std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = i64>>,
|
101 + | i64,
|
102 + | >,
|
103 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = i64>>
|
104 + | {
|
105 + | std::sync::Arc::new(NoopAsync::<i64>(std::marker::PhantomData))
|
106 + | }
|
107 + |
|
108 + | fn create_monotonic_counter(
|
109 + | &self,
|
110 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
111 + | '_,
|
112 + | std::sync::Arc<dyn aws_smithy_observability::instruments::MonotonicCounter>,
|
113 + | >,
|
114 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::MonotonicCounter> {
|
115 + | std::sync::Arc::new(NoopMono)
|
116 + | }
|
117 + |
|
118 + | fn create_async_monotonic_counter(
|
119 + | &self,
|
120 + | _builder: aws_smithy_observability::instruments::AsyncInstrumentBuilder<
|
121 + | '_,
|
122 + | std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = u64>>,
|
123 + | u64,
|
124 + | >,
|
125 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = u64>>
|
126 + | {
|
127 + | std::sync::Arc::new(NoopAsync::<u64>(std::marker::PhantomData))
|
128 + | }
|
129 + |
|
130 + | fn create_histogram(
|
131 + | &self,
|
132 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
133 + | '_,
|
134 + | std::sync::Arc<dyn aws_smithy_observability::instruments::Histogram>,
|
135 + | >,
|
136 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::Histogram> {
|
137 + | std::sync::Arc::new(NoopHist)
|
138 + | }
|
139 + | }
|
140 + |
|
141 + | #[derive(Debug)]
|
142 + | struct NoopAsync<T>(std::marker::PhantomData<T>);
|
143 + | impl<T: Send + Sync + std::fmt::Debug> aws_smithy_observability::instruments::AsyncMeasure
|
144 + | for NoopAsync<T>
|
145 + | {
|
146 + | type Value = T;
|
147 + | fn record(
|
148 + | &self,
|
149 + | _value: T,
|
150 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
151 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
152 + | ) {
|
153 + | }
|
154 + | fn stop(&self) {}
|
155 + | }
|
156 + |
|
157 + | #[derive(Debug)]
|
158 + | struct NoopUpDown;
|
159 + | impl aws_smithy_observability::instruments::UpDownCounter for NoopUpDown {
|
160 + | fn add(
|
161 + | &self,
|
162 + | _value: i64,
|
163 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
164 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
165 + | ) {
|
166 + | }
|
167 + | }
|
168 + |
|
169 + | #[derive(Debug)]
|
170 + | struct NoopMono;
|
171 + | impl aws_smithy_observability::instruments::MonotonicCounter for NoopMono {
|
172 + | fn add(
|
173 + | &self,
|
174 + | _value: u64,
|
175 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
176 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
177 + | ) {
|
178 + | }
|
179 + | }
|
180 + |
|
181 + | #[derive(Debug)]
|
182 + | struct NoopHist;
|
183 + | impl aws_smithy_observability::instruments::Histogram for NoopHist {
|
184 + | fn record(
|
185 + | &self,
|
186 + | _value: f64,
|
187 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
188 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
189 + | ) {
|
190 + | }
|
191 + | }
|
192 + | }
|
193 + |
|
194 + | #[tokio::test]
|
195 + | async fn observability_metrics_in_user_agent() {
|
196 + | // Test case 1: No telemetry provider configured (default noop)
|
197 + | {
|
198 + | let (client, http_client) = test_client(std::convert::identity);
|
199 + | call_operation(client).await;
|
200 + | let req = http_client.actual_requests().last().expect("request");
|
201 + | let user_agent = req.headers().get("x-amz-user-agent").expect("user-agent header");
|
202 + |
|
203 + | // Should NOT contain observability metrics when using noop provider
|
204 + | assert!(!user_agent.contains("m/7")); // OBSERVABILITY_OTEL_METRICS = "7"
|
205 + | }
|
206 + |
|
207 + | // Test case 2: OpenTelemetry metrics provider configured
|
208 + | {
|
209 + | use mock_otel::OtelMeterProvider;
|
210 + |
|
211 + | // Create mock OTel meter provider
|
212 + | let sdk_mp = Arc::new(OtelMeterProvider);
|
213 + |
|
214 + | // Debug: Check what the type name actually is
|
215 + | let type_name = std::any::type_name_of_val(&sdk_mp);
|
216 + | eprintln!("Mock OTel provider Arc type name: {}", type_name);
|
217 + | let type_name2 = std::any::type_name_of_val(sdk_mp.as_ref());
|
218 + | eprintln!("Mock OTel provider as_ref type name: {}", type_name2);
|
219 + |
|
220 + | let sdk_tp = TelemetryProvider::builder().meter_provider(sdk_mp).build();
|
221 + |
|
222 + | // Set global telemetry provider
|
223 + | aws_smithy_observability::global::set_telemetry_provider(sdk_tp).unwrap();
|
224 + |
|
225 + | let (client, http_client) = test_client(std::convert::identity);
|
226 + | call_operation(client).await;
|
227 + | let req = http_client.actual_requests().last().expect("request");
|
228 + | let user_agent = req.headers().get("x-amz-user-agent").expect("user-agent header");
|
229 + |
|
230 + | eprintln!("User-Agent: {}", user_agent);
|
231 + |
|
232 + | // Should contain OBSERVABILITY_OTEL_METRICS metric
|
233 + | assert_ua_contains_metric_values(user_agent, &["7"]);
|
234 + |
|
235 + | // Reset to noop for other tests
|
236 + | aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
|
237 + | .unwrap();
|
238 + | }
|
239 + | }
|