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<
|
79 + | dyn aws_smithy_observability::instruments::AsyncMeasure<Value = f64>,
|
80 + | >,
|
81 + | f64,
|
82 + | >,
|
83 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = f64>>
|
84 + | {
|
85 + | std::sync::Arc::new(NoopAsync::<f64>(std::marker::PhantomData))
|
86 + | }
|
87 + |
|
88 + | fn create_up_down_counter(
|
89 + | &self,
|
90 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
91 + | '_,
|
92 + | std::sync::Arc<dyn aws_smithy_observability::instruments::UpDownCounter>,
|
93 + | >,
|
94 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::UpDownCounter> {
|
95 + | std::sync::Arc::new(NoopUpDown)
|
96 + | }
|
97 + |
|
98 + | fn create_async_up_down_counter(
|
99 + | &self,
|
100 + | _builder: aws_smithy_observability::instruments::AsyncInstrumentBuilder<
|
101 + | '_,
|
102 + | std::sync::Arc<
|
103 + | dyn aws_smithy_observability::instruments::AsyncMeasure<Value = i64>,
|
104 + | >,
|
105 + | i64,
|
106 + | >,
|
107 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = i64>>
|
108 + | {
|
109 + | std::sync::Arc::new(NoopAsync::<i64>(std::marker::PhantomData))
|
110 + | }
|
111 + |
|
112 + | fn create_monotonic_counter(
|
113 + | &self,
|
114 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
115 + | '_,
|
116 + | std::sync::Arc<dyn aws_smithy_observability::instruments::MonotonicCounter>,
|
117 + | >,
|
118 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::MonotonicCounter> {
|
119 + | std::sync::Arc::new(NoopMono)
|
120 + | }
|
121 + |
|
122 + | fn create_async_monotonic_counter(
|
123 + | &self,
|
124 + | _builder: aws_smithy_observability::instruments::AsyncInstrumentBuilder<
|
125 + | '_,
|
126 + | std::sync::Arc<
|
127 + | dyn aws_smithy_observability::instruments::AsyncMeasure<Value = u64>,
|
128 + | >,
|
129 + | u64,
|
130 + | >,
|
131 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::AsyncMeasure<Value = u64>>
|
132 + | {
|
133 + | std::sync::Arc::new(NoopAsync::<u64>(std::marker::PhantomData))
|
134 + | }
|
135 + |
|
136 + | fn create_histogram(
|
137 + | &self,
|
138 + | _builder: aws_smithy_observability::instruments::InstrumentBuilder<
|
139 + | '_,
|
140 + | std::sync::Arc<dyn aws_smithy_observability::instruments::Histogram>,
|
141 + | >,
|
142 + | ) -> std::sync::Arc<dyn aws_smithy_observability::instruments::Histogram> {
|
143 + | std::sync::Arc::new(NoopHist)
|
144 + | }
|
145 + | }
|
146 + |
|
147 + | #[derive(Debug)]
|
148 + | struct NoopAsync<T>(std::marker::PhantomData<T>);
|
149 + | impl<T: Send + Sync + std::fmt::Debug> aws_smithy_observability::instruments::AsyncMeasure
|
150 + | for NoopAsync<T>
|
151 + | {
|
152 + | type Value = T;
|
153 + | fn record(
|
154 + | &self,
|
155 + | _value: T,
|
156 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
157 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
158 + | ) {
|
159 + | }
|
160 + | fn stop(&self) {}
|
161 + | }
|
162 + |
|
163 + | #[derive(Debug)]
|
164 + | struct NoopUpDown;
|
165 + | impl aws_smithy_observability::instruments::UpDownCounter for NoopUpDown {
|
166 + | fn add(
|
167 + | &self,
|
168 + | _value: i64,
|
169 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
170 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
171 + | ) {
|
172 + | }
|
173 + | }
|
174 + |
|
175 + | #[derive(Debug)]
|
176 + | struct NoopMono;
|
177 + | impl aws_smithy_observability::instruments::MonotonicCounter for NoopMono {
|
178 + | fn add(
|
179 + | &self,
|
180 + | _value: u64,
|
181 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
182 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
183 + | ) {
|
184 + | }
|
185 + | }
|
186 + |
|
187 + | #[derive(Debug)]
|
188 + | struct NoopHist;
|
189 + | impl aws_smithy_observability::instruments::Histogram for NoopHist {
|
190 + | fn record(
|
191 + | &self,
|
192 + | _value: f64,
|
193 + | _attributes: Option<&aws_smithy_observability::Attributes>,
|
194 + | _context: Option<&dyn aws_smithy_observability::Context>,
|
195 + | ) {
|
196 + | }
|
197 + | }
|
198 + | }
|
199 + |
|
200 + | #[tokio::test]
|
201 + | async fn observability_metrics_in_user_agent() {
|
202 + | // Test case 1: No telemetry provider configured (default noop)
|
203 + | {
|
204 + | let (client, http_client) = test_client(std::convert::identity);
|
205 + | call_operation(client).await;
|
206 + | let req = http_client.actual_requests().last().expect("request");
|
207 + | let user_agent = req
|
208 + | .headers()
|
209 + | .get("x-amz-user-agent")
|
210 + | .expect("user-agent header");
|
211 + |
|
212 + | // Should NOT contain observability metrics when using noop provider
|
213 + | assert!(!user_agent.contains("m/7")); // OBSERVABILITY_OTEL_METRICS = "7"
|
214 + | }
|
215 + |
|
216 + | // Test case 2: OpenTelemetry metrics provider configured
|
217 + | {
|
218 + | use mock_otel::OtelMeterProvider;
|
219 + |
|
220 + | // Create mock OTel meter provider
|
221 + | let sdk_mp = Arc::new(OtelMeterProvider);
|
222 + |
|
223 + | // Debug: Check what the type name actually is
|
224 + | let type_name = std::any::type_name_of_val(&sdk_mp);
|
225 + | eprintln!("Mock OTel provider Arc type name: {}", type_name);
|
226 + | let type_name2 = std::any::type_name_of_val(sdk_mp.as_ref());
|
227 + | eprintln!("Mock OTel provider as_ref type name: {}", type_name2);
|
228 + |
|
229 + | let sdk_tp = TelemetryProvider::builder().meter_provider(sdk_mp).build();
|
230 + |
|
231 + | // Set global telemetry provider
|
232 + | aws_smithy_observability::global::set_telemetry_provider(sdk_tp).unwrap();
|
233 + |
|
234 + | let (client, http_client) = test_client(std::convert::identity);
|
235 + | call_operation(client).await;
|
236 + | let req = http_client.actual_requests().last().expect("request");
|
237 + | let user_agent = req
|
238 + | .headers()
|
239 + | .get("x-amz-user-agent")
|
240 + | .expect("user-agent header");
|
241 + |
|
242 + | eprintln!("User-Agent: {}", user_agent);
|
243 + |
|
244 + | // Should contain OBSERVABILITY_OTEL_METRICS metric
|
245 + | assert_ua_contains_metric_values(user_agent, &["7"]);
|
246 + |
|
247 + | // Reset to noop for other tests
|
248 + | aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
|
249 + | .unwrap();
|
250 + | }
|
251 + | }
|