6 6 | use aws_config::Region;
|
7 7 | use aws_runtime::{
|
8 8 | sdk_feature::AwsSdkFeature, user_agent::test_util::assert_ua_contains_metric_values,
|
9 9 | };
|
10 10 | use aws_sdk_s3::{
|
11 11 | config::{Intercept, IntoShared},
|
12 12 | primitives::ByteStream,
|
13 13 | Client, Config,
|
14 14 | };
|
15 15 | use aws_smithy_http_client::test_util::capture_request;
|
16 + | use serial_test::serial;
|
16 17 |
|
17 18 | #[derive(Debug)]
|
18 19 | struct TransferManagerFeatureInterceptor;
|
19 20 |
|
20 21 | impl Intercept for TransferManagerFeatureInterceptor {
|
21 22 | fn name(&self) -> &'static str {
|
22 23 | "TransferManagerFeature"
|
23 24 | }
|
24 25 |
|
25 26 | fn read_before_execution(
|
26 27 | &self,
|
27 28 | _ctx: &aws_sdk_s3::config::interceptors::BeforeSerializationInterceptorContextRef<'_>,
|
28 29 | cfg: &mut aws_sdk_s3::config::ConfigBag,
|
29 30 | ) -> Result<(), aws_sdk_s3::error::BoxError> {
|
30 31 | cfg.interceptor_state()
|
31 32 | .store_append(AwsSdkFeature::S3Transfer);
|
32 33 | Ok(())
|
33 34 | }
|
34 35 | }
|
35 36 |
|
36 37 | #[tokio::test]
|
37 38 | async fn test_track_metric_for_s3_transfer_manager() {
|
38 39 | let (http_client, captured_request) = capture_request(None);
|
39 40 | let mut conf_builder = Config::builder()
|
40 41 | .region(Region::new("us-east-1"))
|
41 42 | .http_client(http_client.clone())
|
42 43 | .with_test_defaults();
|
43 44 | // The S3 Transfer Manager uses a passed-in S3 client SDK for operations.
|
44 45 | // By configuring an interceptor at the client level to track metrics,
|
45 46 | // all operations executed by the client will automatically include the metric.
|
46 47 | // This eliminates the need to apply `.config_override` on individual operations
|
47 48 | // to insert the `TransferManagerFeatureInterceptor`.
|
48 49 | conf_builder.push_interceptor(TransferManagerFeatureInterceptor.into_shared());
|
49 50 | let client = Client::from_conf(conf_builder.build());
|
50 51 |
|
51 52 | let _ = client
|
52 53 | .put_object()
|
53 54 | .bucket("doesnotmatter")
|
54 55 | .key("doesnotmatter")
|
55 56 | .body(ByteStream::from_static("Hello, world".as_bytes()))
|
56 57 | .send()
|
57 58 | .await
|
58 59 | .unwrap();
|
59 60 |
|
60 61 | let expected_req = captured_request.expect_request();
|
61 62 | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
62 63 | assert_ua_contains_metric_values(user_agent, &["G"]);
|
63 64 | }
|
65 + |
|
66 + | #[tokio::test]
|
67 + | async fn test_endpoint_override_tracking() {
|
68 + | let (http_client, captured_request) = capture_request(None);
|
69 + | let config = Config::builder()
|
70 + | .region(Region::new("us-east-1"))
|
71 + | .http_client(http_client.clone())
|
72 + | .endpoint_url("http://localhost:9000")
|
73 + | .with_test_defaults()
|
74 + | .build();
|
75 + | let client = Client::from_conf(config);
|
76 + |
|
77 + | let _ = client
|
78 + | .put_object()
|
79 + | .bucket("test-bucket")
|
80 + | .key("test-key")
|
81 + | .body(ByteStream::from_static("test data".as_bytes()))
|
82 + | .send()
|
83 + | .await;
|
84 + |
|
85 + | let expected_req = captured_request.expect_request();
|
86 + | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
87 + | assert_ua_contains_metric_values(user_agent, &["N"]);
|
88 + | }
|
89 + |
|
90 + | #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
91 + | #[serial]
|
92 + | async fn test_observability_metrics_tracking() {
|
93 + | use aws_smithy_observability::{
|
94 + | instruments::{
|
95 + | AsyncInstrumentBuilder, AsyncMeasure, Histogram, InstrumentBuilder, MonotonicCounter,
|
96 + | ProvideInstrument, UpDownCounter,
|
97 + | },
|
98 + | meter::{Meter, ProvideMeter},
|
99 + | Attributes, Context, TelemetryProvider,
|
100 + | };
|
101 + | use std::sync::Arc;
|
102 + |
|
103 + | // Create a test meter provider that is NOT a noop
|
104 + | #[derive(Debug)]
|
105 + | struct TestMeterProvider;
|
106 + |
|
107 + | impl ProvideMeter for TestMeterProvider {
|
108 + | fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter {
|
109 + | Meter::new(Arc::new(TestMeter))
|
110 + | }
|
111 + | }
|
112 + |
|
113 + | #[derive(Debug)]
|
114 + | struct TestMeter;
|
115 + |
|
116 + | impl ProvideInstrument for TestMeter {
|
117 + | fn create_gauge(
|
118 + | &self,
|
119 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = f64>>, f64>,
|
120 + | ) -> Arc<dyn AsyncMeasure<Value = f64>> {
|
121 + | Arc::new(TestAsyncMeasure::<f64>::default())
|
122 + | }
|
123 + |
|
124 + | fn create_up_down_counter(
|
125 + | &self,
|
126 + | _builder: InstrumentBuilder<'_, Arc<dyn UpDownCounter>>,
|
127 + | ) -> Arc<dyn UpDownCounter> {
|
128 + | Arc::new(TestUpDownCounter)
|
129 + | }
|
130 + |
|
131 + | fn create_async_up_down_counter(
|
132 + | &self,
|
133 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = i64>>, i64>,
|
134 + | ) -> Arc<dyn AsyncMeasure<Value = i64>> {
|
135 + | Arc::new(TestAsyncMeasure::<i64>::default())
|
136 + | }
|
137 + |
|
138 + | fn create_monotonic_counter(
|
139 + | &self,
|
140 + | _builder: InstrumentBuilder<'_, Arc<dyn MonotonicCounter>>,
|
141 + | ) -> Arc<dyn MonotonicCounter> {
|
142 + | Arc::new(TestMonotonicCounter)
|
143 + | }
|
144 + |
|
145 + | fn create_async_monotonic_counter(
|
146 + | &self,
|
147 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = u64>>, u64>,
|
148 + | ) -> Arc<dyn AsyncMeasure<Value = u64>> {
|
149 + | Arc::new(TestAsyncMeasure::<u64>::default())
|
150 + | }
|
151 + |
|
152 + | fn create_histogram(
|
153 + | &self,
|
154 + | _builder: InstrumentBuilder<'_, Arc<dyn Histogram>>,
|
155 + | ) -> Arc<dyn Histogram> {
|
156 + | Arc::new(TestHistogram)
|
157 + | }
|
158 + | }
|
159 + |
|
160 + | #[derive(Debug, Default)]
|
161 + | struct TestAsyncMeasure<T: Send + Sync + std::fmt::Debug>(std::marker::PhantomData<T>);
|
162 + |
|
163 + | impl<T: Send + Sync + std::fmt::Debug> AsyncMeasure for TestAsyncMeasure<T> {
|
164 + | type Value = T;
|
165 + |
|
166 + | fn record(
|
167 + | &self,
|
168 + | _value: T,
|
169 + | _attributes: Option<&Attributes>,
|
170 + | _context: Option<&dyn Context>,
|
171 + | ) {
|
172 + | }
|
173 + |
|
174 + | fn stop(&self) {}
|
175 + | }
|
176 + |
|
177 + | #[derive(Debug)]
|
178 + | struct TestUpDownCounter;
|
179 + |
|
180 + | impl UpDownCounter for TestUpDownCounter {
|
181 + | fn add(
|
182 + | &self,
|
183 + | _value: i64,
|
184 + | _attributes: Option<&Attributes>,
|
185 + | _context: Option<&dyn Context>,
|
186 + | ) {
|
187 + | }
|
188 + | }
|
189 + |
|
190 + | #[derive(Debug)]
|
191 + | struct TestMonotonicCounter;
|
192 + |
|
193 + | impl MonotonicCounter for TestMonotonicCounter {
|
194 + | fn add(
|
195 + | &self,
|
196 + | _value: u64,
|
197 + | _attributes: Option<&Attributes>,
|
198 + | _context: Option<&dyn Context>,
|
199 + | ) {
|
200 + | }
|
201 + | }
|
202 + |
|
203 + | #[derive(Debug)]
|
204 + | struct TestHistogram;
|
205 + |
|
206 + | impl Histogram for TestHistogram {
|
207 + | fn record(
|
208 + | &self,
|
209 + | _value: f64,
|
210 + | _attributes: Option<&Attributes>,
|
211 + | _context: Option<&dyn Context>,
|
212 + | ) {
|
213 + | }
|
214 + | }
|
215 + |
|
216 + | // Set up the test meter provider as the global provider BEFORE creating the client
|
217 + | let telemetry_provider = TelemetryProvider::builder()
|
218 + | .meter_provider(Arc::new(TestMeterProvider))
|
219 + | .build();
|
220 + |
|
221 + | // Set the global provider first
|
222 + | aws_smithy_observability::global::set_telemetry_provider(telemetry_provider)
|
223 + | .expect("failed to set telemetry provider");
|
224 + |
|
225 + | // Now create client - the interceptor will detect the non-noop provider
|
226 + | let (http_client, captured_request) = capture_request(None);
|
227 + | let config = Config::builder()
|
228 + | .region(Region::new("us-east-1"))
|
229 + | .http_client(http_client.clone())
|
230 + | .with_test_defaults()
|
231 + | .build();
|
232 + | let client = Client::from_conf(config);
|
233 + |
|
234 + | let _ = client
|
235 + | .put_object()
|
236 + | .bucket("test-bucket")
|
237 + | .key("test-key")
|
238 + | .body(ByteStream::from_static("test data".as_bytes()))
|
239 + | .send()
|
240 + | .await;
|
241 + |
|
242 + | // Verify the User-Agent header contains m/5 (ObservabilityMetrics)
|
243 + | let expected_req = captured_request.expect_request();
|
244 + | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
245 + | assert_ua_contains_metric_values(user_agent, &["5"]);
|
246 + |
|
247 + | // Clean up: reset to noop provider
|
248 + | aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
|
249 + | .expect("failed to reset telemetry provider");
|
250 + | }
|
251 + |
|
252 + | #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
253 + | #[serial]
|
254 + | async fn test_otel_tracing_tracking() {
|
255 + | use aws_smithy_observability::{
|
256 + | instruments::{
|
257 + | AsyncInstrumentBuilder, AsyncMeasure, Histogram, InstrumentBuilder, MonotonicCounter,
|
258 + | ProvideInstrument, UpDownCounter,
|
259 + | },
|
260 + | meter::{Meter, ProvideMeter},
|
261 + | Attributes, Context, TelemetryProvider,
|
262 + | };
|
263 + | use std::sync::Arc;
|
264 + |
|
265 + | // Create a test meter provider that is NOT a noop and is marked as OpenTelemetry
|
266 + | #[derive(Debug)]
|
267 + | struct TestOtelMeterProvider;
|
268 + |
|
269 + | impl ProvideMeter for TestOtelMeterProvider {
|
270 + | fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter {
|
271 + | Meter::new(Arc::new(TestOtelMeter))
|
272 + | }
|
273 + | }
|
274 + |
|
275 + | #[derive(Debug)]
|
276 + | struct TestOtelMeter;
|
277 + |
|
278 + | impl ProvideInstrument for TestOtelMeter {
|
279 + | fn create_gauge(
|
280 + | &self,
|
281 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = f64>>, f64>,
|
282 + | ) -> Arc<dyn AsyncMeasure<Value = f64>> {
|
283 + | Arc::new(TestAsyncMeasure::<f64>::default())
|
284 + | }
|
285 + |
|
286 + | fn create_up_down_counter(
|
287 + | &self,
|
288 + | _builder: InstrumentBuilder<'_, Arc<dyn UpDownCounter>>,
|
289 + | ) -> Arc<dyn UpDownCounter> {
|
290 + | Arc::new(TestUpDownCounter)
|
291 + | }
|
292 + |
|
293 + | fn create_async_up_down_counter(
|
294 + | &self,
|
295 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = i64>>, i64>,
|
296 + | ) -> Arc<dyn AsyncMeasure<Value = i64>> {
|
297 + | Arc::new(TestAsyncMeasure::<i64>::default())
|
298 + | }
|
299 + |
|
300 + | fn create_monotonic_counter(
|
301 + | &self,
|
302 + | _builder: InstrumentBuilder<'_, Arc<dyn MonotonicCounter>>,
|
303 + | ) -> Arc<dyn MonotonicCounter> {
|
304 + | Arc::new(TestMonotonicCounter)
|
305 + | }
|
306 + |
|
307 + | fn create_async_monotonic_counter(
|
308 + | &self,
|
309 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = u64>>, u64>,
|
310 + | ) -> Arc<dyn AsyncMeasure<Value = u64>> {
|
311 + | Arc::new(TestAsyncMeasure::<u64>::default())
|
312 + | }
|
313 + |
|
314 + | fn create_histogram(
|
315 + | &self,
|
316 + | _builder: InstrumentBuilder<'_, Arc<dyn Histogram>>,
|
317 + | ) -> Arc<dyn Histogram> {
|
318 + | Arc::new(TestHistogram)
|
319 + | }
|
320 + | }
|
321 + |
|
322 + | #[derive(Debug, Default)]
|
323 + | struct TestAsyncMeasure<T: Send + Sync + std::fmt::Debug>(std::marker::PhantomData<T>);
|
324 + |
|
325 + | impl<T: Send + Sync + std::fmt::Debug> AsyncMeasure for TestAsyncMeasure<T> {
|
326 + | type Value = T;
|
327 + |
|
328 + | fn record(
|
329 + | &self,
|
330 + | _value: T,
|
331 + | _attributes: Option<&Attributes>,
|
332 + | _context: Option<&dyn Context>,
|
333 + | ) {
|
334 + | }
|
335 + |
|
336 + | fn stop(&self) {}
|
337 + | }
|
338 + |
|
339 + | #[derive(Debug)]
|
340 + | struct TestUpDownCounter;
|
341 + |
|
342 + | impl UpDownCounter for TestUpDownCounter {
|
343 + | fn add(
|
344 + | &self,
|
345 + | _value: i64,
|
346 + | _attributes: Option<&Attributes>,
|
347 + | _context: Option<&dyn Context>,
|
348 + | ) {
|
349 + | }
|
350 + | }
|
351 + |
|
352 + | #[derive(Debug)]
|
353 + | struct TestMonotonicCounter;
|
354 + |
|
355 + | impl MonotonicCounter for TestMonotonicCounter {
|
356 + | fn add(
|
357 + | &self,
|
358 + | _value: u64,
|
359 + | _attributes: Option<&Attributes>,
|
360 + | _context: Option<&dyn Context>,
|
361 + | ) {
|
362 + | }
|
363 + | }
|
364 + |
|
365 + | #[derive(Debug)]
|
366 + | struct TestHistogram;
|
367 + |
|
368 + | impl Histogram for TestHistogram {
|
369 + | fn record(
|
370 + | &self,
|
371 + | _value: f64,
|
372 + | _attributes: Option<&Attributes>,
|
373 + | _context: Option<&dyn Context>,
|
374 + | ) {
|
375 + | }
|
376 + | }
|
377 + |
|
378 + | // Set up the test meter provider as the global provider with OpenTelemetry flag
|
379 + | let telemetry_provider = TelemetryProvider::builder()
|
380 + | .meter_provider(Arc::new(TestOtelMeterProvider))
|
381 + | .with_otel(true) // Mark as OpenTelemetry
|
382 + | .build();
|
383 + |
|
384 + | // Set the global provider first
|
385 + | aws_smithy_observability::global::set_telemetry_provider(telemetry_provider)
|
386 + | .expect("failed to set telemetry provider");
|
387 + |
|
388 + | // Now create client - the plugin will detect the OpenTelemetry provider
|
389 + | let (http_client, captured_request) = capture_request(None);
|
390 + | let config = Config::builder()
|
391 + | .region(Region::new("us-east-1"))
|
392 + | .http_client(http_client.clone())
|
393 + | .with_test_defaults()
|
394 + | .build();
|
395 + | let client = Client::from_conf(config);
|
396 + |
|
397 + | let _ = client
|
398 + | .put_object()
|
399 + | .bucket("test-bucket")
|
400 + | .key("test-key")
|
401 + | .body(ByteStream::from_static("test data".as_bytes()))
|
402 + | .send()
|
403 + | .await;
|
404 + |
|
405 + | // Verify the User-Agent header contains both m/4 (ObservabilityTracing) and m/6 (ObservabilityOtelTracing)
|
406 + | // When OpenTelemetry is enabled, we should see:
|
407 + | // - m/4: ObservabilityTracing (Smithy-level, indicates tracing is enabled)
|
408 + | // - m/5: ObservabilityMetrics (AWS-level, indicates metrics are enabled)
|
409 + | // - m/6: ObservabilityOtelTracing (AWS-level, indicates OpenTelemetry tracing)
|
410 + | // - m/7: ObservabilityOtelMetrics (AWS-level, indicates OpenTelemetry metrics)
|
411 + | let expected_req = captured_request.expect_request();
|
412 + | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
413 + |
|
414 + | assert_ua_contains_metric_values(user_agent, &["4", "5", "6", "7"]);
|
415 + |
|
416 + | // Clean up: reset to noop provider
|
417 + | aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
|
418 + | .expect("failed to reset telemetry provider");
|
419 + | }
|
420 + |
|
421 + | #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
422 + | #[serial]
|
423 + | async fn test_multiple_features_tracked() {
|
424 + | use aws_smithy_observability::{
|
425 + | instruments::{
|
426 + | AsyncInstrumentBuilder, AsyncMeasure, Histogram, InstrumentBuilder, MonotonicCounter,
|
427 + | ProvideInstrument, UpDownCounter,
|
428 + | },
|
429 + | meter::{Meter, ProvideMeter},
|
430 + | Attributes, Context, TelemetryProvider,
|
431 + | };
|
432 + | use std::sync::Arc;
|
433 + |
|
434 + | // Create a test meter provider that is NOT a noop and is marked as OpenTelemetry
|
435 + | #[derive(Debug)]
|
436 + | struct TestOtelMeterProvider;
|
437 + |
|
438 + | impl ProvideMeter for TestOtelMeterProvider {
|
439 + | fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter {
|
440 + | Meter::new(Arc::new(TestOtelMeter))
|
441 + | }
|
442 + | }
|
443 + |
|
444 + | #[derive(Debug)]
|
445 + | struct TestOtelMeter;
|
446 + |
|
447 + | impl ProvideInstrument for TestOtelMeter {
|
448 + | fn create_gauge(
|
449 + | &self,
|
450 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = f64>>, f64>,
|
451 + | ) -> Arc<dyn AsyncMeasure<Value = f64>> {
|
452 + | Arc::new(TestAsyncMeasure::<f64>::default())
|
453 + | }
|
454 + |
|
455 + | fn create_up_down_counter(
|
456 + | &self,
|
457 + | _builder: InstrumentBuilder<'_, Arc<dyn UpDownCounter>>,
|
458 + | ) -> Arc<dyn UpDownCounter> {
|
459 + | Arc::new(TestUpDownCounter)
|
460 + | }
|
461 + |
|
462 + | fn create_async_up_down_counter(
|
463 + | &self,
|
464 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = i64>>, i64>,
|
465 + | ) -> Arc<dyn AsyncMeasure<Value = i64>> {
|
466 + | Arc::new(TestAsyncMeasure::<i64>::default())
|
467 + | }
|
468 + |
|
469 + | fn create_monotonic_counter(
|
470 + | &self,
|
471 + | _builder: InstrumentBuilder<'_, Arc<dyn MonotonicCounter>>,
|
472 + | ) -> Arc<dyn MonotonicCounter> {
|
473 + | Arc::new(TestMonotonicCounter)
|
474 + | }
|
475 + |
|
476 + | fn create_async_monotonic_counter(
|
477 + | &self,
|
478 + | _builder: AsyncInstrumentBuilder<'_, Arc<dyn AsyncMeasure<Value = u64>>, u64>,
|
479 + | ) -> Arc<dyn AsyncMeasure<Value = u64>> {
|
480 + | Arc::new(TestAsyncMeasure::<u64>::default())
|
481 + | }
|
482 + |
|
483 + | fn create_histogram(
|
484 + | &self,
|
485 + | _builder: InstrumentBuilder<'_, Arc<dyn Histogram>>,
|
486 + | ) -> Arc<dyn Histogram> {
|
487 + | Arc::new(TestHistogram)
|
488 + | }
|
489 + | }
|
490 + |
|
491 + | #[derive(Debug, Default)]
|
492 + | struct TestAsyncMeasure<T: Send + Sync + std::fmt::Debug>(std::marker::PhantomData<T>);
|
493 + |
|
494 + | impl<T: Send + Sync + std::fmt::Debug> AsyncMeasure for TestAsyncMeasure<T> {
|
495 + | type Value = T;
|
496 + |
|
497 + | fn record(
|
498 + | &self,
|
499 + | _value: T,
|
500 + | _attributes: Option<&Attributes>,
|
501 + | _context: Option<&dyn Context>,
|
502 + | ) {
|
503 + | }
|
504 + |
|
505 + | fn stop(&self) {}
|
506 + | }
|
507 + |
|
508 + | #[derive(Debug)]
|
509 + | struct TestUpDownCounter;
|
510 + |
|
511 + | impl UpDownCounter for TestUpDownCounter {
|
512 + | fn add(
|
513 + | &self,
|
514 + | _value: i64,
|
515 + | _attributes: Option<&Attributes>,
|
516 + | _context: Option<&dyn Context>,
|
517 + | ) {
|
518 + | }
|
519 + | }
|
520 + |
|
521 + | #[derive(Debug)]
|
522 + | struct TestMonotonicCounter;
|
523 + |
|
524 + | impl MonotonicCounter for TestMonotonicCounter {
|
525 + | fn add(
|
526 + | &self,
|
527 + | _value: u64,
|
528 + | _attributes: Option<&Attributes>,
|
529 + | _context: Option<&dyn Context>,
|
530 + | ) {
|
531 + | }
|
532 + | }
|
533 + |
|
534 + | #[derive(Debug)]
|
535 + | struct TestHistogram;
|
536 + |
|
537 + | impl Histogram for TestHistogram {
|
538 + | fn record(
|
539 + | &self,
|
540 + | _value: f64,
|
541 + | _attributes: Option<&Attributes>,
|
542 + | _context: Option<&dyn Context>,
|
543 + | ) {
|
544 + | }
|
545 + | }
|
546 + |
|
547 + | // Set up the test meter provider as the global provider with OpenTelemetry flag
|
548 + | let telemetry_provider = TelemetryProvider::builder()
|
549 + | .meter_provider(Arc::new(TestOtelMeterProvider))
|
550 + | .with_otel(true) // Mark as OpenTelemetry
|
551 + | .build();
|
552 + |
|
553 + | // Set the global provider first
|
554 + | aws_smithy_observability::global::set_telemetry_provider(telemetry_provider)
|
555 + | .expect("failed to set telemetry provider");
|
556 + |
|
557 + | // Now create client with BOTH custom endpoint AND OpenTelemetry
|
558 + | let (http_client, captured_request) = capture_request(None);
|
559 + | let config = Config::builder()
|
560 + | .region(Region::new("us-east-1"))
|
561 + | .http_client(http_client.clone())
|
562 + | .endpoint_url("http://localhost:9000") // Custom endpoint -> metric N
|
563 + | .with_test_defaults()
|
564 + | .build();
|
565 + | let client = Client::from_conf(config);
|
566 + |
|
567 + | let _ = client
|
568 + | .put_object()
|
569 + | .bucket("test-bucket")
|
570 + | .key("test-key")
|
571 + | .body(ByteStream::from_static("test data".as_bytes()))
|
572 + | .send()
|
573 + | .await;
|
574 + |
|
575 + | // Verify the User-Agent header contains all expected metrics:
|
576 + | // - m/4: ObservabilityTracing (Smithy-level, indicates tracing is enabled)
|
577 + | // - m/5: ObservabilityMetrics (AWS-level, indicates metrics are enabled)
|
578 + | // - m/6: ObservabilityOtelTracing (AWS-level, indicates OpenTelemetry tracing)
|
579 + | // - m/7: ObservabilityOtelMetrics (AWS-level, indicates OpenTelemetry metrics)
|
580 + | // - m/N: EndpointOverride (AWS-level, indicates custom endpoint)
|
581 + | let expected_req = captured_request.expect_request();
|
582 + | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
583 + |
|
584 + | // All metrics should appear in the correct order
|
585 + | assert_ua_contains_metric_values(user_agent, &["4", "5", "6", "7", "N"]);
|
586 + |
|
587 + | // Clean up: reset to noop provider
|
588 + | aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop())
|
589 + | .expect("failed to reset telemetry provider");
|
590 + | }
|
591 + |
|
592 + | #[tokio::test]
|
593 + | async fn test_no_metrics_without_features() {
|
594 + | let (http_client, captured_request) = capture_request(None);
|
595 + | let config = Config::builder()
|
596 + | .region(Region::new("us-east-1"))
|
597 + | .http_client(http_client.clone())
|
598 + | .with_test_defaults()
|
599 + | .build();
|
600 + | let client = Client::from_conf(config);
|
601 + |
|
602 + | let _ = client
|
603 + | .put_object()
|
604 + | .bucket("test-bucket")
|
605 + | .key("test-key")
|
606 + | .body(ByteStream::from_static("test data".as_bytes()))
|
607 + | .send()
|
608 + | .await;
|
609 + |
|
610 + | // Verify the User-Agent header does NOT contain new feature metrics
|
611 + | let expected_req = captured_request.expect_request();
|
612 + | let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap();
|
613 + |
|
614 + | // Assert that none of the new feature metrics appear in the User-Agent header
|
615 + | // New feature metrics: 1 (SsoLoginDevice), 2 (SsoLoginAuth), 4 (ObservabilityTracing),
|
616 + | // 5 (ObservabilityMetrics), 6 (ObservabilityOtelTracing), 7 (ObservabilityOtelMetrics),
|
617 + | // N (EndpointOverride)
|
618 + | assert!(
|
619 + | !user_agent.contains("m/1"),
|
620 + | "User-Agent should not contain m/1 (SsoLoginDevice) without SSO: {}",
|
621 + | user_agent
|
622 + | );
|
623 + | assert!(
|
624 + | !user_agent.contains("m/2"),
|
625 + | "User-Agent should not contain m/2 (SsoLoginAuth) without SSO: {}",
|
626 + | user_agent
|
627 + | );
|
628 + | assert!(
|
629 + | !user_agent.contains("m/4"),
|
630 + | "User-Agent should not contain m/4 (ObservabilityTracing) without observability: {}",
|
631 + | user_agent
|
632 + | );
|
633 + | assert!(
|
634 + | !user_agent.contains("m/5"),
|
635 + | "User-Agent should not contain m/5 (ObservabilityMetrics) without observability: {}",
|
636 + | user_agent
|
637 + | );
|
638 + | assert!(
|
639 + | !user_agent.contains("m/6"),
|
640 + | "User-Agent should not contain m/6 (ObservabilityOtelTracing) without OpenTelemetry: {}",
|
641 + | user_agent
|
642 + | );
|
643 + | assert!(
|
644 + | !user_agent.contains("m/7"),
|
645 + | "User-Agent should not contain m/7 (ObservabilityOtelMetrics) without OpenTelemetry: {}",
|
646 + | user_agent
|
647 + | );
|
648 + | assert!(
|
649 + | !user_agent.contains("m/N"),
|
650 + | "User-Agent should not contain m/N (EndpointOverride) without custom endpoint: {}",
|
651 + | user_agent
|
652 + | );
|
653 + | }
|