From 21a98a36094d7056588a6908cf1b86af139cf5d8 Mon Sep 17 00:00:00 2001 From: vcjana Date: Tue, 11 Nov 2025 22:57:45 -0800 Subject: [PATCH 1/5] fix(observability): Replace Any supertrait with as_any() method --- .../smithy/rustsdk/AwsCodegenDecorator.kt | 2 + .../rustsdk/EndpointOverrideDecorator.kt | 49 ++++++ .../ObservabilityDetectionDecorator.kt | 49 ++++++ .../rustsdk/EndpointOverrideDecoratorTest.kt | 121 +++++++++++++++ aws/rust-runtime/Cargo.lock | 25 +-- aws/rust-runtime/aws-config/Cargo.lock | 5 +- .../aws-credential-types/Cargo.toml | 2 +- aws/rust-runtime/aws-runtime/Cargo.toml | 1 + .../aws-runtime/src/endpoint_override.rs | 144 ++++++++++++++++++ aws/rust-runtime/aws-runtime/src/lib.rs | 6 + .../src/observability_detection.rs | 113 ++++++++++++++ .../aws-runtime/src/observability_plugin.rs | 60 ++++++++ .../aws-runtime/src/sdk_feature.rs | 8 + .../aws-runtime/src/user_agent/interceptor.rs | 9 ++ .../aws-runtime/src/user_agent/metrics.rs | 104 ++++++++++++- .../test_data/feature_id_to_metric_value.json | 6 +- aws/sdk/integration-tests/s3/Cargo.toml | 2 + .../s3/tests/business_metrics.rs | 63 -------- .../generators/EndpointResolverGenerator.kt | 1 + .../integration-tests/Cargo.lock | 6 +- gradle.properties | 2 + rust-runtime/Cargo.lock | 66 ++++---- .../aws-smithy-observability-otel/Cargo.toml | 2 +- .../src/meter.rs | 4 + .../aws-smithy-observability/Cargo.toml | 2 +- .../aws-smithy-observability/src/lib.rs | 2 +- .../aws-smithy-observability/src/meter.rs | 5 +- .../aws-smithy-observability/src/noop.rs | 9 +- .../aws-smithy-observability/src/provider.rs | 26 ++++ rust-runtime/aws-smithy-runtime/Cargo.toml | 2 +- .../src/client/orchestrator/endpoints.rs | 1 + .../src/client/sdk_feature.rs | 1 + tools/ci-build/publisher/Cargo.lock | 4 +- 33 files changed, 778 insertions(+), 124 deletions(-) create mode 100644 aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecorator.kt create mode 100644 aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/ObservabilityDetectionDecorator.kt create mode 100644 aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt create mode 100644 aws/rust-runtime/aws-runtime/src/endpoint_override.rs create mode 100644 aws/rust-runtime/aws-runtime/src/observability_detection.rs create mode 100644 aws/rust-runtime/aws-runtime/src/observability_plugin.rs delete mode 100644 aws/sdk/integration-tests/s3/tests/business_metrics.rs diff --git a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 267fce553b9..19a1b442a6e 100644 --- a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -65,6 +65,8 @@ val DECORATORS: List = AwsRequestIdDecorator(), DisabledAuthDecorator(), RecursionDetectionDecorator(), + EndpointOverrideDecorator(), + ObservabilityDetectionDecorator(), InvocationIdDecorator(), RetryInformationHeaderDecorator(), RemoveDefaultsDecorator(), diff --git a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecorator.kt b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecorator.kt new file mode 100644 index 00000000000..76dd87799fc --- /dev/null +++ b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecorator.kt @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.writable + +/** + * Registers the EndpointOverrideInterceptor to detect custom endpoint usage for business metrics + */ +class EndpointOverrideDecorator : ClientCodegenDecorator { + override val name: String = "EndpointOverride" + override val order: Byte = 0 + + override fun serviceRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = + baseCustomizations + EndpointOverrideRuntimePluginCustomization(codegenContext) + + private class EndpointOverrideRuntimePluginCustomization(codegenContext: ClientCodegenContext) : + ServiceRuntimePluginCustomization() { + private val runtimeConfig = codegenContext.runtimeConfig + private val awsRuntime = AwsRuntimeType.awsRuntime(runtimeConfig) + + override fun section(section: ServiceRuntimePluginSection): Writable = + writable { + when (section) { + is ServiceRuntimePluginSection.RegisterRuntimeComponents -> { + section.registerInterceptor(this) { + rust( + "#T::new()", + awsRuntime.resolve("endpoint_override::EndpointOverrideInterceptor"), + ) + } + } + else -> emptySection + } + } + } +} diff --git a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/ObservabilityDetectionDecorator.kt b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/ObservabilityDetectionDecorator.kt new file mode 100644 index 00000000000..5d674416151 --- /dev/null +++ b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/ObservabilityDetectionDecorator.kt @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext +import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginCustomization +import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection +import software.amazon.smithy.rust.codegen.core.rustlang.Writable +import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.writable + +/** + * Registers the ObservabilityDetectionInterceptor to detect observability feature usage for business metrics + */ +class ObservabilityDetectionDecorator : ClientCodegenDecorator { + override val name: String = "ObservabilityDetection" + override val order: Byte = 0 + + override fun serviceRuntimePluginCustomizations( + codegenContext: ClientCodegenContext, + baseCustomizations: List, + ): List = + baseCustomizations + ObservabilityDetectionRuntimePluginCustomization(codegenContext) + + private class ObservabilityDetectionRuntimePluginCustomization(codegenContext: ClientCodegenContext) : + ServiceRuntimePluginCustomization() { + private val runtimeConfig = codegenContext.runtimeConfig + private val awsRuntime = AwsRuntimeType.awsRuntime(runtimeConfig) + + override fun section(section: ServiceRuntimePluginSection): Writable = + writable { + when (section) { + is ServiceRuntimePluginSection.RegisterRuntimeComponents -> { + section.registerInterceptor(this) { + rust( + "#T::new()", + awsRuntime.resolve("observability_detection::ObservabilityDetectionInterceptor"), + ) + } + } + else -> emptySection + } + } + } +} diff --git a/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt b/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt new file mode 100644 index 00000000000..ee9906d46e5 --- /dev/null +++ b/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rustsdk + +import org.junit.jupiter.api.Test +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope +import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel +import software.amazon.smithy.rust.codegen.core.testutil.integrationTest +import software.amazon.smithy.rust.codegen.core.testutil.tokioTest + +class EndpointOverrideDecoratorTest { + companion object { + private const val PREFIX = "\$version: \"2\"" + val model = + """ + $PREFIX + namespace test + + use aws.api#service + use aws.auth#sigv4 + use aws.protocols#restJson1 + use smithy.rules#endpointRuleSet + + @service(sdkId: "dontcare") + @restJson1 + @sigv4(name: "dontcare") + @auth([sigv4]) + @endpointRuleSet({ + "version": "1.0", + "rules": [{ "type": "endpoint", "conditions": [], "endpoint": { "url": "https://example.com" } }], + "parameters": { + "Region": { "required": false, "type": "String", "builtIn": "AWS::Region" }, + } + }) + service TestService { + version: "2023-01-01", + operations: [SomeOperation] + } + + @http(uri: "/SomeOperation", method: "GET") + @optionalAuth + operation SomeOperation { + input: SomeInput, + output: SomeOutput + } + + @input + structure SomeInput {} + + @output + structure SomeOutput {} + """.asSmithyModel() + } + + @Test + fun `decorator is registered in AwsCodegenDecorator list`() { + // Verify that EndpointOverrideDecorator is in the DECORATORS list + val decoratorNames = DECORATORS.map { it.name } + assert(decoratorNames.contains("EndpointOverride")) { + "EndpointOverrideDecorator should be registered in DECORATORS list" + } + } + + @Test + fun `generated code includes endpoint override interceptor registration`() { + awsSdkIntegrationTest(model) { _, _ -> + // The test passes if the code compiles successfully + // This verifies that the decorator generates valid Rust code + } + } + + @Test + fun `generated code compiles with endpoint override interceptor`() { + // Create custom test params with endpoint_url config enabled + val testParams = + awsIntegrationTestParams().copy( + additionalSettings = + awsIntegrationTestParams().additionalSettings.toBuilder() + .withMember( + "codegen", + software.amazon.smithy.model.node.ObjectNode.builder() + .withMember("includeFluentClient", false) + .withMember("includeEndpointUrlConfig", true) + .build(), + ) + .build(), + ) + + awsSdkIntegrationTest(model, testParams) { context, rustCrate -> + val rc = context.runtimeConfig + val moduleName = context.moduleUseName() + rustCrate.integrationTest("endpoint_override_compiles") { + tokioTest("can_build_client_with_endpoint_url") { + rustTemplate( + """ + use $moduleName::config::Region; + use $moduleName::{Client, Config}; + + let (http_client, _rcvr) = #{capture_request}(#{None}); + let config = Config::builder() + .region(Region::new("us-east-1")) + .endpoint_url("https://custom.example.com") + .http_client(http_client.clone()) + .with_test_defaults() + .build(); + let _client = Client::from_conf(config); + // Test passes if code compiles and client can be created + """, + *preludeScope, + "capture_request" to RuntimeType.captureRequest(rc), + ) + } + } + } + } +} diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index a71c6cb972c..2c34cd69395 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -70,7 +70,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-credential-types" -version = "1.2.9" +version = "1.2.10" dependencies = [ "async-trait", "aws-smithy-async", @@ -121,6 +121,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-eventstream", "aws-smithy-http", + "aws-smithy-observability", "aws-smithy-protocol-test", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -262,7 +263,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.1.5" dependencies = [ "aws-smithy-runtime-api", ] @@ -286,7 +287,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -834,7 +835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1285,7 +1286,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1651,9 +1652,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1828,7 +1829,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2136,9 +2137,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", @@ -2166,7 +2167,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2594,7 +2595,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/aws/rust-runtime/aws-config/Cargo.lock b/aws/rust-runtime/aws-config/Cargo.lock index 261a21d6cc9..b7f1cbd7f7a 100644 --- a/aws/rust-runtime/aws-config/Cargo.lock +++ b/aws/rust-runtime/aws-config/Cargo.lock @@ -108,6 +108,7 @@ dependencies = [ "aws-sigv4", "aws-smithy-async", "aws-smithy-http", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -273,7 +274,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.1.5" dependencies = [ "aws-smithy-runtime-api", ] @@ -305,7 +306,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" dependencies = [ "aws-smithy-async", "aws-smithy-http", diff --git a/aws/rust-runtime/aws-credential-types/Cargo.toml b/aws/rust-runtime/aws-credential-types/Cargo.toml index b9a352d6553..a06192231b7 100644 --- a/aws/rust-runtime/aws-credential-types/Cargo.toml +++ b/aws/rust-runtime/aws-credential-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-credential-types" -version = "1.2.9" +version = "1.2.10" authors = ["AWS Rust SDK Team "] description = "Types for AWS SDK credentials." edition = "2021" diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index 5a8f4bbcc2f..3f8b58938fb 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -21,6 +21,7 @@ aws-sigv4 = { path = "../aws-sigv4", features = ["http0-compat"] } aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } +aws-smithy-observability = { path = "../../../rust-runtime/aws-smithy-observability" } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } diff --git a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs new file mode 100644 index 00000000000..8b38dd8e148 --- /dev/null +++ b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs @@ -0,0 +1,144 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Endpoint override detection for business metrics tracking + +use aws_smithy_runtime_api::box_error::BoxError; +use aws_smithy_runtime_api::client::interceptors::context::BeforeSerializationInterceptorContextRef; +use aws_smithy_runtime_api::client::interceptors::Intercept; +use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; +use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer}; + +use crate::sdk_feature::AwsSdkFeature; + +/// Interceptor that detects custom endpoint URLs for business metrics +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct EndpointOverrideInterceptor; + +impl EndpointOverrideInterceptor { + /// Creates a new EndpointOverrideInterceptor + pub fn new() -> Self { + Self + } +} + +impl Intercept for EndpointOverrideInterceptor { + fn name(&self) -> &'static str { + "EndpointOverrideInterceptor" + } + + fn read_before_execution( + &self, + _context: &BeforeSerializationInterceptorContextRef<'_>, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + // Check if endpoint_url was set in config + if cfg + .load::() + .is_some() + { + cfg.interceptor_state() + .store_append(AwsSdkFeature::EndpointOverride); + } + Ok(()) + } +} + +/// Runtime plugin that detects when a custom endpoint URL has been configured +/// and tracks it for business metrics. +/// +/// This plugin is created by the codegen decorator when a user explicitly +/// sets an endpoint URL via `.endpoint_url()`. It stores the +/// `AwsSdkFeature::EndpointOverride` feature flag in the ConfigBag for +/// business metrics tracking. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct EndpointOverrideRuntimePlugin { + config: Option, +} + +impl EndpointOverrideRuntimePlugin { + /// Creates a new `EndpointOverrideRuntimePlugin` with the given config layer + pub fn new(config: Option) -> Self { + Self { config } + } +} + +impl RuntimePlugin for EndpointOverrideRuntimePlugin { + fn config(&self) -> Option { + self.config.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sdk_feature::AwsSdkFeature; + use aws_smithy_runtime_api::client::interceptors::context::{ + BeforeSerializationInterceptorContextRef, Input, InterceptorContext, + }; + use aws_smithy_types::config_bag::ConfigBag; + + #[test] + fn test_plugin_with_no_config() { + let plugin = EndpointOverrideRuntimePlugin::default(); + assert!(plugin.config().is_none()); + } + + #[test] + fn test_interceptor_detects_endpoint_url_when_present() { + let interceptor = EndpointOverrideInterceptor::new(); + let mut cfg = ConfigBag::base(); + + // Set endpoint URL in config + let endpoint_url = + aws_types::endpoint_config::EndpointUrl("https://custom.example.com".to_string()); + cfg.interceptor_state().store_put(endpoint_url); + + // Create a dummy context + let input = Input::doesnt_matter(); + let ctx = InterceptorContext::new(input); + let context = BeforeSerializationInterceptorContextRef::from(&ctx); + + // Run the interceptor + interceptor + .read_before_execution(&context, &mut cfg) + .unwrap(); + + // Verify feature flag was set in interceptor_state + let features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert_eq!(features.len(), 1); + assert_eq!(features[0], AwsSdkFeature::EndpointOverride); + } + + #[test] + fn test_interceptor_does_not_set_flag_when_endpoint_url_absent() { + let interceptor = EndpointOverrideInterceptor::new(); + let mut cfg = ConfigBag::base(); + + // Create a dummy context + let input = Input::doesnt_matter(); + let ctx = InterceptorContext::new(input); + let context = BeforeSerializationInterceptorContextRef::from(&ctx); + + // Run the interceptor without setting endpoint URL + interceptor + .read_before_execution(&context, &mut cfg) + .unwrap(); + + // Verify no feature flag was set + let features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert_eq!(features.len(), 0); + } +} diff --git a/aws/rust-runtime/aws-runtime/src/lib.rs b/aws/rust-runtime/aws-runtime/src/lib.rs index 702b2ee61d5..8716029de31 100644 --- a/aws/rust-runtime/aws-runtime/src/lib.rs +++ b/aws/rust-runtime/aws-runtime/src/lib.rs @@ -26,6 +26,12 @@ pub mod content_encoding; /// Supporting code for recursion detection in the AWS SDK. pub mod recursion_detection; +/// Supporting code for endpoint override detection in the AWS SDK. +pub mod endpoint_override; + +/// Supporting code for observability feature detection in the AWS SDK. +pub mod observability_detection; + /// Supporting code for user agent headers in the AWS SDK. pub mod user_agent; diff --git a/aws/rust-runtime/aws-runtime/src/observability_detection.rs b/aws/rust-runtime/aws-runtime/src/observability_detection.rs new file mode 100644 index 00000000000..66de3e8f48e --- /dev/null +++ b/aws/rust-runtime/aws-runtime/src/observability_detection.rs @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Observability feature detection for business metrics tracking + +use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; +use aws_smithy_runtime_api::box_error::BoxError; +use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef; +use aws_smithy_runtime_api::client::interceptors::Intercept; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_types::config_bag::ConfigBag; + +use crate::sdk_feature::AwsSdkFeature; + +/// Interceptor that detects when observability features are being used +/// and tracks them for business metrics. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct ObservabilityDetectionInterceptor; + +impl ObservabilityDetectionInterceptor { + /// Creates a new `ObservabilityDetectionInterceptor` + pub fn new() -> Self { + Self + } +} + +impl Intercept for ObservabilityDetectionInterceptor { + fn name(&self) -> &'static str { + "ObservabilityDetectionInterceptor" + } + + fn read_before_signing( + &self, + _context: &BeforeTransmitInterceptorContextRef<'_>, + _runtime_components: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + // Try to get the global telemetry provider + if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() { + // Check if it's not a noop provider + if !provider.is_noop() { + // Track that observability metrics are enabled + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityMetrics); + + // PRAGMATIC APPROACH: Track tracing based on meter provider state + // + // The SDK uses Rust's `tracing` crate globally but doesn't have a + // configurable tracer provider yet. We make the reasonable assumption + // that if a user has configured a meter provider, they've also set up + // tracing as part of their observability stack. + // + // This is a pragmatic workaround until a proper tracer provider is added. + cfg.interceptor_state() + .store_append(SmithySdkFeature::ObservabilityTracing); + + // Check if it's using OpenTelemetry + if provider.is_otel() { + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityOtelMetrics); + + // If using OpenTelemetry for metrics, likely using it for tracing too + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityOtelTracing); + } + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aws_smithy_observability::TelemetryProvider; + use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext}; + use aws_smithy_runtime_api::client::orchestrator::HttpRequest; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + use aws_smithy_types::config_bag::ConfigBag; + + #[test] + fn test_detects_noop_provider() { + let mut context = InterceptorContext::new(Input::doesnt_matter()); + context.enter_serialization_phase(); + context.set_request(HttpRequest::empty()); + let _ = context.take_input(); + context.enter_before_transmit_phase(); + + let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); + let mut cfg = ConfigBag::base(); + + // Set a noop provider (ignore error if already set by another test) + let _ = aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop()); + + let interceptor = ObservabilityDetectionInterceptor::new(); + let ctx = Into::into(&context); + interceptor + .read_before_signing(&ctx, &rc, &mut cfg) + .unwrap(); + + // Should not track any features for noop provider + let features: Vec<_> = cfg.load::().collect(); + assert_eq!(features.len(), 0); + } + + // Note: We cannot easily test non-noop providers without creating a custom meter provider + // implementation, which would require exposing internal types. The noop test above + // is sufficient to verify the detection logic works correctly. +} diff --git a/aws/rust-runtime/aws-runtime/src/observability_plugin.rs b/aws/rust-runtime/aws-runtime/src/observability_plugin.rs new file mode 100644 index 00000000000..aa5aaf0f265 --- /dev/null +++ b/aws/rust-runtime/aws-runtime/src/observability_plugin.rs @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Runtime plugin for detecting observability features + +use aws_smithy_observability::global::get_telemetry_provider; +use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; +use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; +use aws_smithy_types::config_bag::{FrozenLayer, Layer}; +use std::borrow::Cow; + +use crate::sdk_feature::AwsSdkFeature; + +/// Runtime plugin that detects observability features and stores them in the config bag +#[derive(Debug, Default)] +pub struct ObservabilityRuntimePlugin; + +impl ObservabilityRuntimePlugin { + /// Create a new ObservabilityRuntimePlugin + pub fn new() -> Self { + Self + } +} + +impl RuntimePlugin for ObservabilityRuntimePlugin { + fn config(&self) -> Option { + // Try to get the global telemetry provider + if let Ok(telemetry_provider) = get_telemetry_provider() { + // Check if it's a non-noop provider + if !telemetry_provider.is_noop() { + let mut cfg = Layer::new("ObservabilityFeatures"); + + // Store ObservabilityMetrics feature (AWS-level) + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityMetrics); + + // PRAGMATIC ASSUMPTION: + // If someone configured a meter provider, they likely also configured tracing + // (both are part of observability setup). We track ObservabilityTracing based + // on the presence of a non-noop meter provider. + cfg.interceptor_state() + .store_append(SmithySdkFeature::ObservabilityTracing); + + // Check if it's OpenTelemetry + if telemetry_provider.is_otel() { + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityOtelTracing); + cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityOtelMetrics); + } + + return Some(cfg.freeze()); + } + } + + None + } +} diff --git a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs index a1d03e4aa23..0c09bde697b 100644 --- a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs +++ b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs @@ -24,6 +24,14 @@ pub enum AwsSdkFeature { SsoLoginDevice, /// Calling an SSO-OIDC operation as part of the SSO login flow, when using the OAuth2.0 authorization code grant SsoLoginAuth, + /// An operation called with observability metrics collection enabled + ObservabilityMetrics, + /// An operation called with OpenTelemetry tracing integration enabled + ObservabilityOtelTracing, + /// An operation called with OpenTelemetry metrics integration enabled + ObservabilityOtelMetrics, + /// An operation called using a user provided endpoint URL + EndpointOverride, } impl Storable for AwsSdkFeature { diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs b/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs index 9b36162be0d..b5438c84261 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs @@ -133,6 +133,7 @@ impl Intercept for UserAgentInterceptor { .expect("`AwsUserAgent should have been created in `read_before_execution`") .clone(); + // Load features from both the main config bag and interceptor_state let smithy_sdk_features = cfg.load::(); for smithy_sdk_feature in smithy_sdk_features { smithy_sdk_feature @@ -147,6 +148,14 @@ impl Intercept for UserAgentInterceptor { .map(|m| ua.add_business_metric(m)); } + // Also load AWS SDK features from interceptor_state (where interceptors store them) + let aws_sdk_features_from_interceptor = cfg.interceptor_state().load::(); + for aws_sdk_feature in aws_sdk_features_from_interceptor { + aws_sdk_feature + .provide_business_metric() + .map(|m| ua.add_business_metric(m)); + } + let aws_credential_features = cfg.load::(); for aws_credential_feature in aws_credential_features { aws_credential_feature diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs index f8497216aca..f664b368c51 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs @@ -164,7 +164,11 @@ iterable_enum!( CredentialsImds, SsoLoginDevice, SsoLoginAuth, - BearerServiceEnvVars + BearerServiceEnvVars, + ObservabilityTracing, + ObservabilityMetrics, + ObservabilityOtelTracing, + ObservabilityOtelMetrics ); pub(crate) trait ProvideBusinessMetric { @@ -198,6 +202,7 @@ impl ProvideBusinessMetric for SmithySdkFeature { FlexibleChecksumsResWhenRequired => { Some(BusinessMetric::FlexibleChecksumsResWhenRequired) } + ObservabilityTracing => Some(BusinessMetric::ObservabilityTracing), otherwise => { // This may occur if a customer upgrades only the `aws-smithy-runtime-api` crate // while continuing to use an outdated version of an SDK crate or the `aws-runtime` @@ -222,6 +227,10 @@ impl ProvideBusinessMetric for AwsSdkFeature { S3Transfer => Some(BusinessMetric::S3Transfer), SsoLoginDevice => Some(BusinessMetric::SsoLoginDevice), SsoLoginAuth => Some(BusinessMetric::SsoLoginAuth), + ObservabilityMetrics => Some(BusinessMetric::ObservabilityMetrics), + ObservabilityOtelTracing => Some(BusinessMetric::ObservabilityOtelTracing), + ObservabilityOtelMetrics => Some(BusinessMetric::ObservabilityOtelMetrics), + EndpointOverride => Some(BusinessMetric::EndpointOverride), } } } @@ -400,4 +409,97 @@ mod tests { let csv = "A,B"; assert_eq!("A,B", drop_unfinished_metrics_to_fit(csv, 5)); } + + #[test] + fn test_aws_sdk_feature_mappings() { + use crate::sdk_feature::AwsSdkFeature; + use crate::user_agent::metrics::ProvideBusinessMetric; + + // Test ObservabilityMetrics mapping + assert_eq!( + AwsSdkFeature::ObservabilityMetrics.provide_business_metric(), + Some(BusinessMetric::ObservabilityMetrics) + ); + + // Test ObservabilityOtelTracing mapping + assert_eq!( + AwsSdkFeature::ObservabilityOtelTracing.provide_business_metric(), + Some(BusinessMetric::ObservabilityOtelTracing) + ); + + // Test ObservabilityOtelMetrics mapping + assert_eq!( + AwsSdkFeature::ObservabilityOtelMetrics.provide_business_metric(), + Some(BusinessMetric::ObservabilityOtelMetrics) + ); + + // Test SsoLoginDevice mapping + assert_eq!( + AwsSdkFeature::SsoLoginDevice.provide_business_metric(), + Some(BusinessMetric::SsoLoginDevice) + ); + + // Test SsoLoginAuth mapping + assert_eq!( + AwsSdkFeature::SsoLoginAuth.provide_business_metric(), + Some(BusinessMetric::SsoLoginAuth) + ); + + // Test EndpointOverride mapping + assert_eq!( + AwsSdkFeature::EndpointOverride.provide_business_metric(), + Some(BusinessMetric::EndpointOverride) + ); + } + + #[test] + fn test_smithy_sdk_feature_observability_tracing_mapping() { + use crate::user_agent::metrics::ProvideBusinessMetric; + use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; + + // Test ObservabilityTracing mapping + assert_eq!( + SmithySdkFeature::ObservabilityTracing.provide_business_metric(), + Some(BusinessMetric::ObservabilityTracing) + ); + } + + #[test] + fn test_metric_id_values() { + // Test that metric IDs match the expected values from FEATURES.md specification + + // SSO Login metrics + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::SsoLoginDevice), + Some(&"1".into()) + ); + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::SsoLoginAuth), + Some(&"2".into()) + ); + + // Observability metrics + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::ObservabilityTracing), + Some(&"4".into()) + ); + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::ObservabilityMetrics), + Some(&"5".into()) + ); + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::ObservabilityOtelTracing), + Some(&"6".into()) + ); + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::ObservabilityOtelMetrics), + Some(&"7".into()) + ); + + // Endpoint Override metric + assert_eq!( + FEATURE_ID_TO_METRIC_VALUE.get(&BusinessMetric::EndpointOverride), + Some(&"N".into()) + ); + } } diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/test_data/feature_id_to_metric_value.json b/aws/rust-runtime/aws-runtime/src/user_agent/test_data/feature_id_to_metric_value.json index 4630f3d270f..bea91c26c9e 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/test_data/feature_id_to_metric_value.json +++ b/aws/rust-runtime/aws-runtime/src/user_agent/test_data/feature_id_to_metric_value.json @@ -54,5 +54,9 @@ "CREDENTIALS_IMDS": "0", "SSO_LOGIN_DEVICE": "1", "SSO_LOGIN_AUTH": "2", - "BEARER_SERVICE_ENV_VARS": "3" + "BEARER_SERVICE_ENV_VARS": "3", + "OBSERVABILITY_TRACING": "4", + "OBSERVABILITY_METRICS": "5", + "OBSERVABILITY_OTEL_TRACING": "6", + "OBSERVABILITY_OTEL_METRICS": "7" } diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index 8125b8d11b3..632383cc5a6 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -22,6 +22,7 @@ aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime", features = ["test- aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", features = ["test-util", "behavior-version-latest"] } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util", "rt-tokio"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-observability = { path = "../../build/aws-sdk/sdk/aws-smithy-observability" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util", "http-1x"] } @@ -49,6 +50,7 @@ tracing-subscriber = { version = "0.3.15", features = ["env-filter", "json"] } # If you're writing a test with this, take heed! `no-env-filter` means you'll be capturing # logs from everything that speaks, so be specific with your asserts. tracing-test = { version = "0.2.4", features = ["no-env-filter"] } +serial_test = "3" [dependencies] pin-project-lite = "0.2.13" diff --git a/aws/sdk/integration-tests/s3/tests/business_metrics.rs b/aws/sdk/integration-tests/s3/tests/business_metrics.rs deleted file mode 100644 index 2fc8616d589..00000000000 --- a/aws/sdk/integration-tests/s3/tests/business_metrics.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use aws_config::Region; -use aws_runtime::{ - sdk_feature::AwsSdkFeature, user_agent::test_util::assert_ua_contains_metric_values, -}; -use aws_sdk_s3::{ - config::{Intercept, IntoShared}, - primitives::ByteStream, - Client, Config, -}; -use aws_smithy_http_client::test_util::capture_request; - -#[derive(Debug)] -struct TransferManagerFeatureInterceptor; - -impl Intercept for TransferManagerFeatureInterceptor { - fn name(&self) -> &'static str { - "TransferManagerFeature" - } - - fn read_before_execution( - &self, - _ctx: &aws_sdk_s3::config::interceptors::BeforeSerializationInterceptorContextRef<'_>, - cfg: &mut aws_sdk_s3::config::ConfigBag, - ) -> Result<(), aws_sdk_s3::error::BoxError> { - cfg.interceptor_state() - .store_append(AwsSdkFeature::S3Transfer); - Ok(()) - } -} - -#[tokio::test] -async fn test_track_metric_for_s3_transfer_manager() { - let (http_client, captured_request) = capture_request(None); - let mut conf_builder = Config::builder() - .region(Region::new("us-east-1")) - .http_client(http_client.clone()) - .with_test_defaults(); - // The S3 Transfer Manager uses a passed-in S3 client SDK for operations. - // By configuring an interceptor at the client level to track metrics, - // all operations executed by the client will automatically include the metric. - // This eliminates the need to apply `.config_override` on individual operations - // to insert the `TransferManagerFeatureInterceptor`. - conf_builder.push_interceptor(TransferManagerFeatureInterceptor.into_shared()); - let client = Client::from_conf(conf_builder.build()); - - let _ = client - .put_object() - .bucket("doesnotmatter") - .key("doesnotmatter") - .body(ByteStream::from_static("Hello, world".as_bytes())) - .send() - .await - .unwrap(); - - let expected_req = captured_request.expect_request(); - let user_agent = expected_req.headers().get("x-amz-user-agent").unwrap(); - assert_ua_contains_metric_values(user_agent, &["G"]); -} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt index 3ce3bf3dd69..c32665500a0 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt @@ -387,6 +387,7 @@ internal class EndpointResolverGenerator( val url = generator.generate(endpoint.url) val headers = endpoint.headers.mapValues { entry -> entry.value.map { generator.generate(it) } } val properties = endpoint.properties.mapValues { entry -> generator.generate(entry.value) } + return writable { rustTemplate("#{SmithyEndpoint}::builder().url(#{url:W})", *codegenScope, "url" to url) headers.forEach { (name, values) -> values.forEach { rust(".header(${name.dq()}, #W)", it) } } diff --git a/codegen-server-test/integration-tests/Cargo.lock b/codegen-server-test/integration-tests/Cargo.lock index b752b100e92..6dcbf8bb2af 100644 --- a/codegen-server-test/integration-tests/Cargo.lock +++ b/codegen-server-test/integration-tests/Cargo.lock @@ -120,7 +120,7 @@ dependencies = [ [[package]] name = "aws-smithy-http-server" -version = "0.65.8" +version = "0.65.10" dependencies = [ "aws-smithy-cbor", "aws-smithy-http", @@ -155,7 +155,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.1.5" dependencies = [ "aws-smithy-runtime-api", ] @@ -179,7 +179,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" dependencies = [ "aws-smithy-async", "aws-smithy-http", diff --git a/gradle.properties b/gradle.properties index d0333b3cf29..54facbc47b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,6 +12,8 @@ org.gradle.jvmargs=-Xmx1024M smithy.rs.runtime.crate.stable.version=1.1.7 # Version number to use for the generated unstable runtime crates smithy.rs.runtime.crate.unstable.version=0.60.6 +# Version number for aws-smithy-observability crate +smithy.rs.runtime.crate.version.aws-smithy-observability=0.1.5 kotlin.code.style=official allowLocalDeps=false # Avoid registering dependencies/plugins/tasks that are only used for testing purposes diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index cadb749539b..13fed90cb64 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -220,7 +220,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -237,7 +237,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -552,7 +552,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.2.0" dependencies = [ "aws-smithy-runtime-api", "serial_test", @@ -600,7 +600,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" dependencies = [ "approx", "aws-smithy-async", @@ -777,7 +777,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -1245,7 +1245,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -1278,7 +1278,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -1436,7 +1436,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2294,9 +2294,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minicbor" -version = "0.24.2" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8e213c36148d828083ae01948eed271d03f95f7e72571fa242d78184029af2" +checksum = "29be4f60e41fde478b36998b88821946aafac540e53591e76db53921a0cc225b" dependencies = [ "half", "minicbor-derive", @@ -2310,7 +2310,7 @@ checksum = "bd2209fff77f705b00c737016a48e73733d7fbccb8b007194db148f03561fb70" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2563,7 +2563,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2678,7 +2678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2784,7 +2784,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2797,7 +2797,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -2819,9 +2819,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -3348,7 +3348,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3410,7 +3410,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3551,9 +3551,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.108" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", @@ -3568,7 +3568,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3658,7 +3658,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3669,7 +3669,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3786,7 +3786,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -3948,7 +3948,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -4021,7 +4021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -4223,7 +4223,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", "wasm-bindgen-shared", ] @@ -4617,7 +4617,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", "synstructure", ] @@ -4638,7 +4638,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] [[package]] @@ -4658,7 +4658,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", "synstructure", ] @@ -4698,5 +4698,5 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.108", + "syn 2.0.109", ] diff --git a/rust-runtime/aws-smithy-observability-otel/Cargo.toml b/rust-runtime/aws-smithy-observability-otel/Cargo.toml index 0c9a790891a..5eb9007ae34 100644 --- a/rust-runtime/aws-smithy-observability-otel/Cargo.toml +++ b/rust-runtime/aws-smithy-observability-otel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-observability-otel" -version = "0.1.2" +version = "0.1.3" authors = [ "AWS Rust SDK Team ", ] diff --git a/rust-runtime/aws-smithy-observability-otel/src/meter.rs b/rust-runtime/aws-smithy-observability-otel/src/meter.rs index e30a4e578e5..b65ce9fcf3a 100644 --- a/rust-runtime/aws-smithy-observability-otel/src/meter.rs +++ b/rust-runtime/aws-smithy-observability-otel/src/meter.rs @@ -286,6 +286,10 @@ impl ProvideMeter for OtelMeterProvider { fn get_meter(&self, scope: &'static str, _attributes: Option<&Attributes>) -> Meter { Meter::new(Arc::new(MeterWrap(self.meter_provider.meter(scope)))) } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } #[cfg(test)] diff --git a/rust-runtime/aws-smithy-observability/Cargo.toml b/rust-runtime/aws-smithy-observability/Cargo.toml index bcf5d6e8ef1..2ae6578df48 100644 --- a/rust-runtime/aws-smithy-observability/Cargo.toml +++ b/rust-runtime/aws-smithy-observability/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.2.0" authors = [ "AWS Rust SDK Team ", ] diff --git a/rust-runtime/aws-smithy-observability/src/lib.rs b/rust-runtime/aws-smithy-observability/src/lib.rs index c4e53dbb4e0..fa40ef04b6f 100644 --- a/rust-runtime/aws-smithy-observability/src/lib.rs +++ b/rust-runtime/aws-smithy-observability/src/lib.rs @@ -25,7 +25,7 @@ mod error; pub use error::{ErrorKind, GlobalTelemetryProviderError, ObservabilityError}; pub mod global; pub mod meter; -mod noop; +pub mod noop; mod provider; pub use provider::{TelemetryProvider, TelemetryProviderBuilder}; pub mod instruments; diff --git a/rust-runtime/aws-smithy-observability/src/meter.rs b/rust-runtime/aws-smithy-observability/src/meter.rs index 2cca5743d46..dd2ba8ac1a8 100644 --- a/rust-runtime/aws-smithy-observability/src/meter.rs +++ b/rust-runtime/aws-smithy-observability/src/meter.rs @@ -14,9 +14,12 @@ use crate::{attributes::Attributes, instruments::ProvideInstrument}; use std::{borrow::Cow, fmt::Debug, sync::Arc}; /// Provides named instances of [Meter]. -pub trait ProvideMeter: Send + Sync + Debug { +pub trait ProvideMeter: Send + Sync + Debug + 'static { /// Get or create a named [Meter]. fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter; + + /// Downcast to `Any` for type inspection. + fn as_any(&self) -> &dyn std::any::Any; } /// The entry point to creating instruments. A grouping of related metrics. diff --git a/rust-runtime/aws-smithy-observability/src/noop.rs b/rust-runtime/aws-smithy-observability/src/noop.rs index 6a3b9f47307..925cc2d2fac 100644 --- a/rust-runtime/aws-smithy-observability/src/noop.rs +++ b/rust-runtime/aws-smithy-observability/src/noop.rs @@ -18,12 +18,19 @@ use crate::{ meter::{Meter, ProvideMeter}, }; +/// A no-op implementation of [`ProvideMeter`] that creates no-op meters. +/// +/// This provider is useful for testing or when observability is disabled. #[derive(Debug)] -pub(crate) struct NoopMeterProvider; +pub struct NoopMeterProvider; impl ProvideMeter for NoopMeterProvider { fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter { Meter::new(Arc::new(NoopMeter)) } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } #[derive(Debug)] diff --git a/rust-runtime/aws-smithy-observability/src/provider.rs b/rust-runtime/aws-smithy-observability/src/provider.rs index 564f1cd9e6e..f5450905676 100644 --- a/rust-runtime/aws-smithy-observability/src/provider.rs +++ b/rust-runtime/aws-smithy-observability/src/provider.rs @@ -13,6 +13,7 @@ use crate::{meter::ProvideMeter, noop::NoopMeterProvider}; #[non_exhaustive] pub struct TelemetryProvider { meter_provider: Arc, + is_otel: bool, } impl TelemetryProvider { @@ -20,6 +21,7 @@ impl TelemetryProvider { pub fn builder() -> TelemetryProviderBuilder { TelemetryProviderBuilder { meter_provider: Arc::new(NoopMeterProvider), + is_otel: false, } } @@ -27,6 +29,7 @@ impl TelemetryProvider { pub fn noop() -> TelemetryProvider { Self { meter_provider: Arc::new(NoopMeterProvider), + is_otel: false, } } @@ -34,6 +37,20 @@ impl TelemetryProvider { pub fn meter_provider(&self) -> &(dyn ProvideMeter + Send + Sync) { self.meter_provider.as_ref() } + + /// Returns true if this provider is using OpenTelemetry + pub fn is_otel(&self) -> bool { + self.is_otel + } + + /// Returns true if this provider is a noop provider + pub fn is_noop(&self) -> bool { + // Check if the meter provider is the NoopMeterProvider by attempting to downcast + self.meter_provider + .as_any() + .downcast_ref::() + .is_some() + } } // If we choose to expand our Telemetry provider and make Logging and Tracing @@ -44,6 +61,7 @@ impl Default for TelemetryProvider { fn default() -> Self { Self { meter_provider: Arc::new(NoopMeterProvider), + is_otel: false, } } } @@ -52,6 +70,7 @@ impl Default for TelemetryProvider { #[non_exhaustive] pub struct TelemetryProviderBuilder { meter_provider: Arc, + is_otel: bool, } impl TelemetryProviderBuilder { @@ -61,10 +80,17 @@ impl TelemetryProviderBuilder { self } + /// Mark this provider as using OpenTelemetry. + pub fn with_otel(mut self, is_otel: bool) -> Self { + self.is_otel = is_otel; + self + } + /// Build the [TelemetryProvider]. pub fn build(self) -> TelemetryProvider { TelemetryProvider { meter_provider: self.meter_provider, + is_otel: self.is_otel, } } } diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index d426d8f3657..0dfd36fbae6 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "The new smithy runtime crate" edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs index 48d308eb32b..f5bdd7eaed9 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs @@ -47,6 +47,7 @@ impl ResolveEndpoint for StaticUriEndpointResolver { fn resolve_endpoint<'a>(&'a self, _params: &'a EndpointResolverParams) -> EndpointFuture<'a> { EndpointFuture::ready(Ok(Endpoint::builder() .url(self.endpoint.to_string()) + .property("is_custom_endpoint", true) .build())) } } diff --git a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs index 254458dc914..9a4d0826cc4 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs @@ -23,6 +23,7 @@ pub enum SmithySdkFeature { FlexibleChecksumsReqWhenRequired, FlexibleChecksumsResWhenSupported, FlexibleChecksumsResWhenRequired, + ObservabilityTracing, } impl Storable for SmithySdkFeature { diff --git a/tools/ci-build/publisher/Cargo.lock b/tools/ci-build/publisher/Cargo.lock index 3ccfc5fbb28..20194c02c03 100644 --- a/tools/ci-build/publisher/Cargo.lock +++ b/tools/ci-build/publisher/Cargo.lock @@ -973,7 +973,7 @@ dependencies = [ [[package]] name = "publisher" -version = "0.4.2" +version = "0.4.3" dependencies = [ "anyhow", "async-recursion", @@ -1314,7 +1314,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithy-rs-tool-common" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", From 81ee539a0a42f27c076cd9385f8db8809b674007 Mon Sep 17 00:00:00 2001 From: vcjana Date: Wed, 12 Nov 2025 00:29:49 -0800 Subject: [PATCH 2/5] Add default implementation for as_any() --- rust-runtime/aws-smithy-observability/src/meter.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust-runtime/aws-smithy-observability/src/meter.rs b/rust-runtime/aws-smithy-observability/src/meter.rs index dd2ba8ac1a8..71df70af8ea 100644 --- a/rust-runtime/aws-smithy-observability/src/meter.rs +++ b/rust-runtime/aws-smithy-observability/src/meter.rs @@ -19,7 +19,13 @@ pub trait ProvideMeter: Send + Sync + Debug + 'static { fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter; /// Downcast to `Any` for type inspection. - fn as_any(&self) -> &dyn std::any::Any; + /// + /// The default implementation returns a reference to `()`, which will fail + /// any downcast attempts. Implementors should override this method to return + /// `self` for proper type inspection support. + fn as_any(&self) -> &dyn std::any::Any { + &() + } } /// The entry point to creating instruments. A grouping of related metrics. From 903f0433ccaedbc6b7d12e7e18b171261415a625 Mon Sep 17 00:00:00 2001 From: vcjana Date: Fri, 14 Nov 2025 14:06:31 -0800 Subject: [PATCH 3/5] Address code review feedback: improve tests, use type-safe OTel detection, and remove redundant code --- .../smithy/rustsdk/AwsCodegenDecorator.kt | 2 +- .../rustsdk/EndpointOverrideDecoratorTest.kt | 59 ++- aws/rust-runtime/Cargo.lock | 448 +++++++++++++++++- aws/rust-runtime/aws-runtime/Cargo.toml | 1 + .../aws-runtime/src/endpoint_override.rs | 111 +++-- .../src/observability_detection.rs | 128 +++-- .../aws-runtime/src/observability_plugin.rs | 60 --- .../aws-runtime/src/sdk_feature.rs | 6 - .../aws-runtime/src/user_agent/interceptor.rs | 46 +- .../aws-runtime/src/user_agent/metrics.rs | 31 +- aws/sdk/integration-tests/s3/Cargo.toml | 1 - .../integration-tests/Cargo.lock | 4 +- gradle.properties | 2 - rust-runtime/Cargo.lock | 2 +- .../aws-smithy-observability/src/lib.rs | 1 + .../aws-smithy-observability/src/meter.rs | 109 ++++- .../aws-smithy-observability/src/provider.rs | 26 - .../src/client/orchestrator/endpoints.rs | 1 - .../src/client/sdk_feature.rs | 2 +- 19 files changed, 790 insertions(+), 250 deletions(-) delete mode 100644 aws/rust-runtime/aws-runtime/src/observability_plugin.rs diff --git a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt index 19a1b442a6e..9d4b3b0e02e 100644 --- a/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +++ b/aws/codegen-aws-sdk/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt @@ -46,6 +46,7 @@ val DECORATORS: List = CredentialsProviderDecorator(), RegionDecorator(), RequireEndpointRules(), + EndpointOverrideDecorator(), UserAgentDecorator(), SigV4AuthDecorator(), HttpRequestChecksumDecorator(), @@ -65,7 +66,6 @@ val DECORATORS: List = AwsRequestIdDecorator(), DisabledAuthDecorator(), RecursionDetectionDecorator(), - EndpointOverrideDecorator(), ObservabilityDetectionDecorator(), InvocationIdDecorator(), RetryInformationHeaderDecorator(), diff --git a/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt b/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt index ee9906d46e5..c59778a695a 100644 --- a/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt +++ b/aws/codegen-aws-sdk/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointOverrideDecoratorTest.kt @@ -67,16 +67,7 @@ class EndpointOverrideDecoratorTest { } @Test - fun `generated code includes endpoint override interceptor registration`() { - awsSdkIntegrationTest(model) { _, _ -> - // The test passes if the code compiles successfully - // This verifies that the decorator generates valid Rust code - } - } - - @Test - fun `generated code compiles with endpoint override interceptor`() { - // Create custom test params with endpoint_url config enabled + fun `endpoint override interceptor adds business metric to user agent`() { val testParams = awsIntegrationTestParams().copy( additionalSettings = @@ -94,22 +85,58 @@ class EndpointOverrideDecoratorTest { awsSdkIntegrationTest(model, testParams) { context, rustCrate -> val rc = context.runtimeConfig val moduleName = context.moduleUseName() - rustCrate.integrationTest("endpoint_override_compiles") { - tokioTest("can_build_client_with_endpoint_url") { + rustCrate.integrationTest("endpoint_override_functional") { + tokioTest("interceptor_adds_metric_when_endpoint_overridden") { rustTemplate( """ use $moduleName::config::Region; use $moduleName::{Client, Config}; - let (http_client, _rcvr) = #{capture_request}(#{None}); + let (http_client, rcvr) = #{capture_request}(#{None}); let config = Config::builder() .region(Region::new("us-east-1")) .endpoint_url("https://custom.example.com") .http_client(http_client.clone()) - .with_test_defaults() .build(); - let _client = Client::from_conf(config); - // Test passes if code compiles and client can be created + let client = Client::from_conf(config); + + // CRITICAL: Actually make a request + let _ = client.some_operation().send().await; + + // Capture and verify the request + let request = rcvr.expect_request(); + + // Verify endpoint was overridden + let uri = request.uri().to_string(); + assert!( + uri.starts_with("https://custom.example.com"), + "Expected custom endpoint, got: {}", + uri + ); + + // Verify x-amz-user-agent contains business metric 'N' (endpoint override) + // The metric appears in the business-metrics section as "m/..." with comma-separated IDs + let x_amz_user_agent = request.headers() + .get("x-amz-user-agent") + .expect("x-amz-user-agent header missing"); + + // Extract the business metrics section (starts with "m/") + let has_endpoint_override_metric = x_amz_user_agent + .split_whitespace() + .find(|part| part.starts_with("m/")) + .map(|metrics| { + // Check if 'N' appears as a metric ID (either alone or in a comma-separated list) + metrics.strip_prefix("m/") + .map(|ids| ids.split(',').any(|id| id == "N")) + .unwrap_or(false) + }) + .unwrap_or(false); + + assert!( + has_endpoint_override_metric, + "Expected metric ID 'N' (endpoint override) in x-amz-user-agent business metrics, got: {}", + x_amz_user_agent + ); """, *preludeScope, "capture_request" to RuntimeType.captureRequest(rc), diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index c21ced915df..28d70caa92e 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -45,6 +45,156 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.89" @@ -122,6 +272,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-observability", + "aws-smithy-observability-otel", "aws-smithy-protocol-test", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -263,11 +414,23 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.5" +version = "0.2.0" dependencies = [ "aws-smithy-runtime-api", ] +[[package]] +name = "aws-smithy-observability-otel" +version = "0.1.3" +dependencies = [ + "async-global-executor", + "async-task", + "aws-smithy-observability", + "opentelemetry", + "opentelemetry_sdk", + "value-bag", +] + [[package]] name = "aws-smithy-protocol-test" version = "0.63.6" @@ -282,7 +445,7 @@ dependencies = [ "regex-lite", "roxmltree", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -422,6 +585,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bs58" version = "0.5.1" @@ -555,6 +731,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -618,7 +803,7 @@ checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" dependencies = [ "crc", "digest", - "rand", + "rand 0.9.2", "regex", "rustversion", ] @@ -835,7 +1020,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", ] [[package]] @@ -896,6 +1108,47 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -915,9 +1168,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-macro", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -953,6 +1209,24 @@ dependencies = [ "wasip2", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.12.1" @@ -1286,7 +1560,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1314,6 +1588,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1352,6 +1635,9 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +dependencies = [ + "value-bag", +] [[package]] name = "lru" @@ -1486,6 +1772,42 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "opentelemetry" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570074cc999d1a58184080966e5bd3bf3a9a4af650c3b05047c2621e7405cd17" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror 1.0.69", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c627d9f4c9cdc1f21a29ee4bfbd6028fcb8bcf2a857b43f3abdf72c9c862f3" +dependencies = [ + "async-std", + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", +] + [[package]] name = "outref" version = "0.5.2" @@ -1503,6 +1825,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1544,6 +1872,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -1582,6 +1921,20 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -1635,8 +1988,8 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand", - "rand_chacha", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -1665,16 +2018,37 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.3", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -1829,7 +2203,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2167,7 +2541,16 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] @@ -2176,7 +2559,18 @@ version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2302,6 +2696,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -2473,6 +2878,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "version_check" version = "0.9.5" @@ -2541,6 +2952,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.105" @@ -2595,7 +3019,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index 3f8b58938fb..eaac3f0322b 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -22,6 +22,7 @@ aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } aws-smithy-observability = { path = "../../../rust-runtime/aws-smithy-observability" } +aws-smithy-observability-otel = { path = "../../../rust-runtime/aws-smithy-observability-otel" } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } diff --git a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs index 8b38dd8e148..79c14b28811 100644 --- a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs +++ b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs @@ -6,14 +6,19 @@ //! Endpoint override detection for business metrics tracking use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::interceptors::context::BeforeSerializationInterceptorContextRef; +use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef; use aws_smithy_runtime_api::client::interceptors::Intercept; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; -use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer}; +use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer}; use crate::sdk_feature::AwsSdkFeature; /// Interceptor that detects custom endpoint URLs for business metrics +/// +/// This interceptor checks at runtime if a `StaticUriEndpointResolver` is configured, +/// which indicates that `.endpoint_url()` was called. When detected, it stores the +/// `AwsSdkFeature::EndpointOverride` feature flag for business metrics tracking. #[derive(Debug, Default)] #[non_exhaustive] pub struct EndpointOverrideInterceptor; @@ -30,19 +35,25 @@ impl Intercept for EndpointOverrideInterceptor { "EndpointOverrideInterceptor" } - fn read_before_execution( + fn read_after_serialization( &self, - _context: &BeforeSerializationInterceptorContextRef<'_>, + _context: &BeforeTransmitInterceptorContextRef<'_>, + runtime_components: &RuntimeComponents, cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - // Check if endpoint_url was set in config - if cfg - .load::() - .is_some() - { + // Check if the endpoint resolver is a StaticUriEndpointResolver + // This indicates that .endpoint_url() was called + let resolver = runtime_components.endpoint_resolver(); + + // Check the resolver's debug string to see if it's StaticUriEndpointResolver + let debug_str = format!("{:?}", resolver); + + if debug_str.contains("StaticUriEndpointResolver") { + // Store in interceptor_state cfg.interceptor_state() .store_append(AwsSdkFeature::EndpointOverride); } + Ok(()) } } @@ -65,6 +76,15 @@ impl EndpointOverrideRuntimePlugin { pub fn new(config: Option) -> Self { Self { config } } + + /// Creates a new `EndpointOverrideRuntimePlugin` and marks that endpoint override is enabled + pub fn new_with_feature_flag() -> Self { + let mut layer = Layer::new("endpoint_override"); + layer.store_append(AwsSdkFeature::EndpointOverride); + Self { + config: Some(layer.freeze()), + } + } } impl RuntimePlugin for EndpointOverrideRuntimePlugin { @@ -77,10 +97,6 @@ impl RuntimePlugin for EndpointOverrideRuntimePlugin { mod tests { use super::*; use crate::sdk_feature::AwsSdkFeature; - use aws_smithy_runtime_api::client::interceptors::context::{ - BeforeSerializationInterceptorContextRef, Input, InterceptorContext, - }; - use aws_smithy_types::config_bag::ConfigBag; #[test] fn test_plugin_with_no_config() { @@ -89,56 +105,55 @@ mod tests { } #[test] - fn test_interceptor_detects_endpoint_url_when_present() { - let interceptor = EndpointOverrideInterceptor::new(); - let mut cfg = ConfigBag::base(); - - // Set endpoint URL in config - let endpoint_url = - aws_types::endpoint_config::EndpointUrl("https://custom.example.com".to_string()); - cfg.interceptor_state().store_put(endpoint_url); - - // Create a dummy context - let input = Input::doesnt_matter(); - let ctx = InterceptorContext::new(input); - let context = BeforeSerializationInterceptorContextRef::from(&ctx); - - // Run the interceptor - interceptor - .read_before_execution(&context, &mut cfg) - .unwrap(); + fn test_plugin_with_feature_flag() { + let plugin = EndpointOverrideRuntimePlugin::new_with_feature_flag(); + let config = plugin.config().expect("config should be set"); - // Verify feature flag was set in interceptor_state - let features: Vec<_> = cfg - .interceptor_state() - .load::() - .cloned() - .collect(); + // Verify the feature flag is present in the config + let features: Vec<_> = config.load::().cloned().collect(); assert_eq!(features.len(), 1); assert_eq!(features[0], AwsSdkFeature::EndpointOverride); } #[test] - fn test_interceptor_does_not_set_flag_when_endpoint_url_absent() { - let interceptor = EndpointOverrideInterceptor::new(); + fn test_interceptor_detects_static_uri_resolver() { + use aws_smithy_runtime::client::orchestrator::endpoints::StaticUriEndpointResolver; + use aws_smithy_runtime_api::client::endpoint::SharedEndpointResolver; + use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext}; + use aws_smithy_runtime_api::client::orchestrator::HttpRequest; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + use aws_smithy_types::config_bag::ConfigBag; + + // Create a StaticUriEndpointResolver + let endpoint_resolver = SharedEndpointResolver::new(StaticUriEndpointResolver::uri( + "https://custom.example.com", + )); + + let mut context = InterceptorContext::new(Input::doesnt_matter()); + context.enter_serialization_phase(); + context.set_request(HttpRequest::empty()); + let _ = context.take_input(); + context.enter_before_transmit_phase(); + + let rc = RuntimeComponentsBuilder::for_tests() + .with_endpoint_resolver(Some(endpoint_resolver)) + .build() + .unwrap(); let mut cfg = ConfigBag::base(); - // Create a dummy context - let input = Input::doesnt_matter(); - let ctx = InterceptorContext::new(input); - let context = BeforeSerializationInterceptorContextRef::from(&ctx); - - // Run the interceptor without setting endpoint URL + let interceptor = EndpointOverrideInterceptor::new(); + let ctx = Into::into(&context); interceptor - .read_before_execution(&context, &mut cfg) + .read_after_serialization(&ctx, &rc, &mut cfg) .unwrap(); - // Verify no feature flag was set + // Verify the feature flag was set let features: Vec<_> = cfg .interceptor_state() .load::() .cloned() .collect(); - assert_eq!(features.len(), 0); + assert_eq!(features.len(), 1, "Expected 1 feature, got: {:?}", features); + assert_eq!(features[0], AwsSdkFeature::EndpointOverride); } } diff --git a/aws/rust-runtime/aws-runtime/src/observability_detection.rs b/aws/rust-runtime/aws-runtime/src/observability_detection.rs index 66de3e8f48e..08623d14f63 100644 --- a/aws/rust-runtime/aws-runtime/src/observability_detection.rs +++ b/aws/rust-runtime/aws-runtime/src/observability_detection.rs @@ -4,7 +4,13 @@ */ //! Observability feature detection for business metrics tracking +//! +//! This module provides an interceptor for detecting observability features in the AWS SDK: +//! +//! - [`ObservabilityDetectionInterceptor`]: Detects observability features during +//! request processing and tracks them for business metrics in the User-Agent header. +use aws_smithy_observability_otel::meter::OtelMeterProvider; use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef; @@ -12,8 +18,6 @@ use aws_smithy_runtime_api::client::interceptors::Intercept; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_types::config_bag::ConfigBag; -use crate::sdk_feature::AwsSdkFeature; - /// Interceptor that detects when observability features are being used /// and tracks them for business metrics. #[derive(Debug, Default)] @@ -40,32 +44,18 @@ impl Intercept for ObservabilityDetectionInterceptor { ) -> Result<(), BoxError> { // Try to get the global telemetry provider if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() { - // Check if it's not a noop provider - if !provider.is_noop() { + // Use type-safe downcasting to detect OpenTelemetry meter provider + // This works with any ProvideMeter implementation and doesn't require + // implementation-specific boolean flags + if provider + .meter_provider() + .as_any() + .downcast_ref::() + .is_some() + { // Track that observability metrics are enabled cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityMetrics); - - // PRAGMATIC APPROACH: Track tracing based on meter provider state - // - // The SDK uses Rust's `tracing` crate globally but doesn't have a - // configurable tracer provider yet. We make the reasonable assumption - // that if a user has configured a meter provider, they've also set up - // tracing as part of their observability stack. - // - // This is a pragmatic workaround until a proper tracer provider is added. - cfg.interceptor_state() - .store_append(SmithySdkFeature::ObservabilityTracing); - - // Check if it's using OpenTelemetry - if provider.is_otel() { - cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityOtelMetrics); - - // If using OpenTelemetry for metrics, likely using it for tracing too - cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityOtelTracing); - } + .store_append(SmithySdkFeature::ObservabilityMetrics); } } @@ -76,6 +66,7 @@ impl Intercept for ObservabilityDetectionInterceptor { #[cfg(test)] mod tests { use super::*; + use crate::sdk_feature::AwsSdkFeature; use aws_smithy_observability::TelemetryProvider; use aws_smithy_runtime_api::client::interceptors::context::{Input, InterceptorContext}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; @@ -102,12 +93,85 @@ mod tests { .read_before_signing(&ctx, &rc, &mut cfg) .unwrap(); - // Should not track any features for noop provider - let features: Vec<_> = cfg.load::().collect(); - assert_eq!(features.len(), 0); + // Should not track any features for noop provider since it doesn't downcast to OtelMeterProvider + let smithy_features: Vec<_> = cfg.load::().collect(); + assert_eq!(smithy_features.len(), 0); + + let aws_features: Vec<_> = cfg.load::().collect(); + assert_eq!(aws_features.len(), 0); } - // Note: We cannot easily test non-noop providers without creating a custom meter provider - // implementation, which would require exposing internal types. The noop test above - // is sufficient to verify the detection logic works correctly. + #[test] + fn test_custom_provider_not_detected_as_otel() { + use aws_smithy_observability::meter::{Meter, ProvideMeter}; + use aws_smithy_observability::noop::NoopMeterProvider; + use aws_smithy_observability::Attributes; + use std::sync::Arc; + + // Create a custom (non-OTel, non-noop) meter provider + // This simulates a user implementing their own metrics provider + #[derive(Debug)] + struct CustomMeterProvider { + inner: NoopMeterProvider, + } + + impl ProvideMeter for CustomMeterProvider { + fn get_meter(&self, scope: &'static str, attributes: Option<&Attributes>) -> Meter { + // Delegate to noop for simplicity, but this is a distinct type + self.inner.get_meter(scope, attributes) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + + let mut context = InterceptorContext::new(Input::doesnt_matter()); + context.enter_serialization_phase(); + context.set_request(HttpRequest::empty()); + let _ = context.take_input(); + context.enter_before_transmit_phase(); + + let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); + let mut cfg = ConfigBag::base(); + + // Set the custom provider + let custom_provider = Arc::new(CustomMeterProvider { + inner: NoopMeterProvider, + }); + let telemetry_provider = TelemetryProvider::builder() + .meter_provider(custom_provider) + .build(); + let _ = aws_smithy_observability::global::set_telemetry_provider(telemetry_provider); + + let interceptor = ObservabilityDetectionInterceptor::new(); + let ctx = Into::into(&context); + interceptor + .read_before_signing(&ctx, &rc, &mut cfg) + .unwrap(); + + // Should NOT track any features for custom provider since it doesn't downcast to OtelMeterProvider + // The new implementation only emits metrics when OTel is detected + let smithy_features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert!( + !smithy_features.iter().any(|f| *f == SmithySdkFeature::ObservabilityMetrics), + "Should not detect custom provider as having observability metrics (only OTel is tracked)" + ); + + // Verify no AWS-specific features are tracked for custom provider + let aws_features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert_eq!( + aws_features.len(), + 0, + "Should not track any AWS-specific features for custom provider" + ); + } } diff --git a/aws/rust-runtime/aws-runtime/src/observability_plugin.rs b/aws/rust-runtime/aws-runtime/src/observability_plugin.rs deleted file mode 100644 index aa5aaf0f265..00000000000 --- a/aws/rust-runtime/aws-runtime/src/observability_plugin.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Runtime plugin for detecting observability features - -use aws_smithy_observability::global::get_telemetry_provider; -use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; -use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin; -use aws_smithy_types::config_bag::{FrozenLayer, Layer}; -use std::borrow::Cow; - -use crate::sdk_feature::AwsSdkFeature; - -/// Runtime plugin that detects observability features and stores them in the config bag -#[derive(Debug, Default)] -pub struct ObservabilityRuntimePlugin; - -impl ObservabilityRuntimePlugin { - /// Create a new ObservabilityRuntimePlugin - pub fn new() -> Self { - Self - } -} - -impl RuntimePlugin for ObservabilityRuntimePlugin { - fn config(&self) -> Option { - // Try to get the global telemetry provider - if let Ok(telemetry_provider) = get_telemetry_provider() { - // Check if it's a non-noop provider - if !telemetry_provider.is_noop() { - let mut cfg = Layer::new("ObservabilityFeatures"); - - // Store ObservabilityMetrics feature (AWS-level) - cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityMetrics); - - // PRAGMATIC ASSUMPTION: - // If someone configured a meter provider, they likely also configured tracing - // (both are part of observability setup). We track ObservabilityTracing based - // on the presence of a non-noop meter provider. - cfg.interceptor_state() - .store_append(SmithySdkFeature::ObservabilityTracing); - - // Check if it's OpenTelemetry - if telemetry_provider.is_otel() { - cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityOtelTracing); - cfg.interceptor_state() - .store_append(AwsSdkFeature::ObservabilityOtelMetrics); - } - - return Some(cfg.freeze()); - } - } - - None - } -} diff --git a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs index 0c09bde697b..7c1d437627b 100644 --- a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs +++ b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs @@ -24,12 +24,6 @@ pub enum AwsSdkFeature { SsoLoginDevice, /// Calling an SSO-OIDC operation as part of the SSO login flow, when using the OAuth2.0 authorization code grant SsoLoginAuth, - /// An operation called with observability metrics collection enabled - ObservabilityMetrics, - /// An operation called with OpenTelemetry tracing integration enabled - ObservabilityOtelTracing, - /// An operation called with OpenTelemetry metrics integration enabled - ObservabilityOtelMetrics, /// An operation called using a user provided endpoint URL EndpointOverride, } diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs b/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs index b5438c84261..c1abd2c8d8c 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent/interceptor.rs @@ -133,7 +133,10 @@ impl Intercept for UserAgentInterceptor { .expect("`AwsUserAgent should have been created in `read_before_execution`") .clone(); - // Load features from both the main config bag and interceptor_state + // Load features from ConfigBag. Note: cfg.load() automatically captures features + // from all layers in the ConfigBag, including the interceptor_state layer where + // interceptors store their features. There is no need to separately call + // cfg.interceptor_state().load() as that would be redundant. let smithy_sdk_features = cfg.load::(); for smithy_sdk_feature in smithy_sdk_features { smithy_sdk_feature @@ -148,14 +151,6 @@ impl Intercept for UserAgentInterceptor { .map(|m| ua.add_business_metric(m)); } - // Also load AWS SDK features from interceptor_state (where interceptors store them) - let aws_sdk_features_from_interceptor = cfg.interceptor_state().load::(); - for aws_sdk_feature in aws_sdk_features_from_interceptor { - aws_sdk_feature - .provide_business_metric() - .map(|m| ua.add_business_metric(m)); - } - let aws_credential_features = cfg.load::(); for aws_credential_feature in aws_credential_features { aws_credential_feature @@ -351,4 +346,37 @@ mod tests { expect_header(&context, "x-amz-user-agent") ); } + + #[test] + fn test_cfg_load_captures_all_feature_layers() { + use crate::sdk_feature::AwsSdkFeature; + + // Create a ConfigBag with features in both base layer and interceptor_state + let mut base_layer = Layer::new("base"); + base_layer.store_append(AwsSdkFeature::EndpointOverride); + + let mut config = ConfigBag::of_layers(vec![base_layer]); + + // Store a feature in interceptor_state (simulating what interceptors do) + config + .interceptor_state() + .store_append(AwsSdkFeature::SsoLoginDevice); + + // Verify that cfg.load() captures features from all layers + let all_features: Vec<&AwsSdkFeature> = config.load::().collect(); + + assert_eq!( + all_features.len(), + 2, + "cfg.load() should capture features from all layers" + ); + assert!( + all_features.contains(&&AwsSdkFeature::EndpointOverride), + "should contain feature from base layer" + ); + assert!( + all_features.contains(&&AwsSdkFeature::SsoLoginDevice), + "should contain feature from interceptor_state" + ); + } } diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs index f664b368c51..66e6081f0ca 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs @@ -202,7 +202,7 @@ impl ProvideBusinessMetric for SmithySdkFeature { FlexibleChecksumsResWhenRequired => { Some(BusinessMetric::FlexibleChecksumsResWhenRequired) } - ObservabilityTracing => Some(BusinessMetric::ObservabilityTracing), + ObservabilityMetrics => Some(BusinessMetric::ObservabilityMetrics), otherwise => { // This may occur if a customer upgrades only the `aws-smithy-runtime-api` crate // while continuing to use an outdated version of an SDK crate or the `aws-runtime` @@ -227,9 +227,6 @@ impl ProvideBusinessMetric for AwsSdkFeature { S3Transfer => Some(BusinessMetric::S3Transfer), SsoLoginDevice => Some(BusinessMetric::SsoLoginDevice), SsoLoginAuth => Some(BusinessMetric::SsoLoginAuth), - ObservabilityMetrics => Some(BusinessMetric::ObservabilityMetrics), - ObservabilityOtelTracing => Some(BusinessMetric::ObservabilityOtelTracing), - ObservabilityOtelMetrics => Some(BusinessMetric::ObservabilityOtelMetrics), EndpointOverride => Some(BusinessMetric::EndpointOverride), } } @@ -415,24 +412,6 @@ mod tests { use crate::sdk_feature::AwsSdkFeature; use crate::user_agent::metrics::ProvideBusinessMetric; - // Test ObservabilityMetrics mapping - assert_eq!( - AwsSdkFeature::ObservabilityMetrics.provide_business_metric(), - Some(BusinessMetric::ObservabilityMetrics) - ); - - // Test ObservabilityOtelTracing mapping - assert_eq!( - AwsSdkFeature::ObservabilityOtelTracing.provide_business_metric(), - Some(BusinessMetric::ObservabilityOtelTracing) - ); - - // Test ObservabilityOtelMetrics mapping - assert_eq!( - AwsSdkFeature::ObservabilityOtelMetrics.provide_business_metric(), - Some(BusinessMetric::ObservabilityOtelMetrics) - ); - // Test SsoLoginDevice mapping assert_eq!( AwsSdkFeature::SsoLoginDevice.provide_business_metric(), @@ -453,14 +432,14 @@ mod tests { } #[test] - fn test_smithy_sdk_feature_observability_tracing_mapping() { + fn test_smithy_sdk_feature_observability_mappings() { use crate::user_agent::metrics::ProvideBusinessMetric; use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; - // Test ObservabilityTracing mapping + // Test ObservabilityMetrics mapping assert_eq!( - SmithySdkFeature::ObservabilityTracing.provide_business_metric(), - Some(BusinessMetric::ObservabilityTracing) + SmithySdkFeature::ObservabilityMetrics.provide_business_metric(), + Some(BusinessMetric::ObservabilityMetrics) ); } diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index 632383cc5a6..a1c3139cc43 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -22,7 +22,6 @@ aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime", features = ["test- aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", features = ["test-util", "behavior-version-latest"] } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util", "rt-tokio"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } -aws-smithy-observability = { path = "../../build/aws-sdk/sdk/aws-smithy-observability" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util", "http-1x"] } diff --git a/codegen-server-test/integration-tests/Cargo.lock b/codegen-server-test/integration-tests/Cargo.lock index 6dcbf8bb2af..7d3e7979cdf 100644 --- a/codegen-server-test/integration-tests/Cargo.lock +++ b/codegen-server-test/integration-tests/Cargo.lock @@ -120,7 +120,7 @@ dependencies = [ [[package]] name = "aws-smithy-http-server" -version = "0.65.10" +version = "0.65.9" dependencies = [ "aws-smithy-cbor", "aws-smithy-http", @@ -155,7 +155,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability" -version = "0.1.5" +version = "0.2.0" dependencies = [ "aws-smithy-runtime-api", ] diff --git a/gradle.properties b/gradle.properties index 54facbc47b8..d0333b3cf29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,8 +12,6 @@ org.gradle.jvmargs=-Xmx1024M smithy.rs.runtime.crate.stable.version=1.1.7 # Version number to use for the generated unstable runtime crates smithy.rs.runtime.crate.unstable.version=0.60.6 -# Version number for aws-smithy-observability crate -smithy.rs.runtime.crate.version.aws-smithy-observability=0.1.5 kotlin.code.style=official allowLocalDeps=false # Avoid registering dependencies/plugins/tasks that are only used for testing purposes diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index 40bc4182233..402580ef095 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "aws-smithy-observability-otel" -version = "0.1.2" +version = "0.1.3" dependencies = [ "async-global-executor", "async-task", diff --git a/rust-runtime/aws-smithy-observability/src/lib.rs b/rust-runtime/aws-smithy-observability/src/lib.rs index fa40ef04b6f..767cb890af3 100644 --- a/rust-runtime/aws-smithy-observability/src/lib.rs +++ b/rust-runtime/aws-smithy-observability/src/lib.rs @@ -25,6 +25,7 @@ mod error; pub use error::{ErrorKind, GlobalTelemetryProviderError, ObservabilityError}; pub mod global; pub mod meter; +#[doc(hidden)] pub mod noop; mod provider; pub use provider::{TelemetryProvider, TelemetryProviderBuilder}; diff --git a/rust-runtime/aws-smithy-observability/src/meter.rs b/rust-runtime/aws-smithy-observability/src/meter.rs index 71df70af8ea..395a1c64c9b 100644 --- a/rust-runtime/aws-smithy-observability/src/meter.rs +++ b/rust-runtime/aws-smithy-observability/src/meter.rs @@ -20,12 +20,32 @@ pub trait ProvideMeter: Send + Sync + Debug + 'static { /// Downcast to `Any` for type inspection. /// - /// The default implementation returns a reference to `()`, which will fail - /// any downcast attempts. Implementors should override this method to return - /// `self` for proper type inspection support. - fn as_any(&self) -> &dyn std::any::Any { - &() - } + /// This method enables runtime type checking via downcasting to concrete types. + /// + /// Implementations must return `self` to enable proper downcasting: + /// + /// ```ignore + /// impl ProvideMeter for MyProvider { + /// fn as_any(&self) -> &dyn std::any::Any { + /// self + /// } + /// } + /// ``` + /// + /// # Example Usage + /// + /// ```ignore + /// use aws_smithy_observability::meter::ProvideMeter; + /// use aws_smithy_observability_otel::meter::OtelMeterProvider; + /// + /// fn check_provider_type(provider: &dyn ProvideMeter) { + /// // Downcast to check if this is an OpenTelemetry provider + /// if provider.as_any().downcast_ref::().is_some() { + /// println!("This is an OpenTelemetry provider"); + /// } + /// } + /// ``` + fn as_any(&self) -> &dyn std::any::Any; } /// The entry point to creating instruments. A grouping of related metrics. @@ -105,3 +125,80 @@ impl Meter { InstrumentBuilder::new(self, name.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::noop::NoopMeterProvider; + + #[test] + fn test_as_any_downcasting_works() { + // Create a noop provider + let provider = NoopMeterProvider; + + // Convert to trait object + let provider_dyn: &dyn ProvideMeter = &provider; + + // Test that downcasting works when as_any() is properly implemented + let downcast_result = provider_dyn.as_any().downcast_ref::(); + assert!( + downcast_result.is_some(), + "Downcasting should succeed when as_any() returns self" + ); + } + + /// Custom meter provider for testing extensibility. + /// + /// This demonstrates that users can create their own meter provider implementations + /// and that all required types are publicly accessible. + #[derive(Debug)] + struct CustomMeterProvider; + + impl ProvideMeter for CustomMeterProvider { + fn get_meter(&self, _scope: &'static str, _attributes: Option<&Attributes>) -> Meter { + // Create a meter using NoopMeterProvider's implementation + // This demonstrates that users can compose their own providers + let noop_provider = NoopMeterProvider; + noop_provider.get_meter(_scope, _attributes) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + + #[test] + fn test_custom_provider_extensibility() { + // Create a custom provider + let custom = CustomMeterProvider; + let provider_ref: &dyn ProvideMeter = &custom; + + // Verify custom provider doesn't downcast to NoopMeterProvider + assert!( + provider_ref + .as_any() + .downcast_ref::() + .is_none(), + "Custom provider should not downcast to NoopMeterProvider" + ); + + // Verify custom provider downcasts to its own type + assert!( + provider_ref + .as_any() + .downcast_ref::() + .is_some(), + "Custom provider should downcast to CustomMeterProvider" + ); + + // Verify the provider can create meters (demonstrates all required types are accessible) + let meter = custom.get_meter("test_scope", None); + + // Verify we can create instruments from the meter + let _counter = meter.create_monotonic_counter("test_counter").build(); + let _histogram = meter.create_histogram("test_histogram").build(); + let _up_down = meter.create_up_down_counter("test_up_down").build(); + + // If we got here, all required types are publicly accessible + } +} diff --git a/rust-runtime/aws-smithy-observability/src/provider.rs b/rust-runtime/aws-smithy-observability/src/provider.rs index f5450905676..564f1cd9e6e 100644 --- a/rust-runtime/aws-smithy-observability/src/provider.rs +++ b/rust-runtime/aws-smithy-observability/src/provider.rs @@ -13,7 +13,6 @@ use crate::{meter::ProvideMeter, noop::NoopMeterProvider}; #[non_exhaustive] pub struct TelemetryProvider { meter_provider: Arc, - is_otel: bool, } impl TelemetryProvider { @@ -21,7 +20,6 @@ impl TelemetryProvider { pub fn builder() -> TelemetryProviderBuilder { TelemetryProviderBuilder { meter_provider: Arc::new(NoopMeterProvider), - is_otel: false, } } @@ -29,7 +27,6 @@ impl TelemetryProvider { pub fn noop() -> TelemetryProvider { Self { meter_provider: Arc::new(NoopMeterProvider), - is_otel: false, } } @@ -37,20 +34,6 @@ impl TelemetryProvider { pub fn meter_provider(&self) -> &(dyn ProvideMeter + Send + Sync) { self.meter_provider.as_ref() } - - /// Returns true if this provider is using OpenTelemetry - pub fn is_otel(&self) -> bool { - self.is_otel - } - - /// Returns true if this provider is a noop provider - pub fn is_noop(&self) -> bool { - // Check if the meter provider is the NoopMeterProvider by attempting to downcast - self.meter_provider - .as_any() - .downcast_ref::() - .is_some() - } } // If we choose to expand our Telemetry provider and make Logging and Tracing @@ -61,7 +44,6 @@ impl Default for TelemetryProvider { fn default() -> Self { Self { meter_provider: Arc::new(NoopMeterProvider), - is_otel: false, } } } @@ -70,7 +52,6 @@ impl Default for TelemetryProvider { #[non_exhaustive] pub struct TelemetryProviderBuilder { meter_provider: Arc, - is_otel: bool, } impl TelemetryProviderBuilder { @@ -80,17 +61,10 @@ impl TelemetryProviderBuilder { self } - /// Mark this provider as using OpenTelemetry. - pub fn with_otel(mut self, is_otel: bool) -> Self { - self.is_otel = is_otel; - self - } - /// Build the [TelemetryProvider]. pub fn build(self) -> TelemetryProvider { TelemetryProvider { meter_provider: self.meter_provider, - is_otel: self.is_otel, } } } diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs index f5bdd7eaed9..48d308eb32b 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/endpoints.rs @@ -47,7 +47,6 @@ impl ResolveEndpoint for StaticUriEndpointResolver { fn resolve_endpoint<'a>(&'a self, _params: &'a EndpointResolverParams) -> EndpointFuture<'a> { EndpointFuture::ready(Ok(Endpoint::builder() .url(self.endpoint.to_string()) - .property("is_custom_endpoint", true) .build())) } } diff --git a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs index 9a4d0826cc4..cc02a266825 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs @@ -23,7 +23,7 @@ pub enum SmithySdkFeature { FlexibleChecksumsReqWhenRequired, FlexibleChecksumsResWhenSupported, FlexibleChecksumsResWhenRequired, - ObservabilityTracing, + ObservabilityMetrics, } impl Storable for SmithySdkFeature { From 204d06482730ba8b17a7e261a2b6b890cc32a39a Mon Sep 17 00:00:00 2001 From: vcjana Date: Sun, 16 Nov 2025 02:28:12 -0800 Subject: [PATCH 4/5] Fix clippy uninlined_format_args warning --- aws/rust-runtime/aws-runtime/src/endpoint_override.rs | 2 +- aws/rust-runtime/aws-runtime/src/observability_detection.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs index 79c14b28811..6a1f41bedc4 100644 --- a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs +++ b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs @@ -46,7 +46,7 @@ impl Intercept for EndpointOverrideInterceptor { let resolver = runtime_components.endpoint_resolver(); // Check the resolver's debug string to see if it's StaticUriEndpointResolver - let debug_str = format!("{:?}", resolver); + let debug_str = format!("{resolver:?}"); if debug_str.contains("StaticUriEndpointResolver") { // Store in interceptor_state diff --git a/aws/rust-runtime/aws-runtime/src/observability_detection.rs b/aws/rust-runtime/aws-runtime/src/observability_detection.rs index 08623d14f63..ae8a2cb87aa 100644 --- a/aws/rust-runtime/aws-runtime/src/observability_detection.rs +++ b/aws/rust-runtime/aws-runtime/src/observability_detection.rs @@ -7,7 +7,7 @@ //! //! This module provides an interceptor for detecting observability features in the AWS SDK: //! -//! - [`ObservabilityDetectionInterceptor`]: Detects observability features during +//! - [`crate::observability_detection::ObservabilityDetectionInterceptor`]: Detects observability features during //! request processing and tracks them for business metrics in the User-Agent header. use aws_smithy_observability_otel::meter::OtelMeterProvider; From 8f9ec079ebc00a1e651c90a2b2da17697329cc76 Mon Sep 17 00:00:00 2001 From: vcjana Date: Mon, 17 Nov 2025 12:53:09 -0800 Subject: [PATCH 5/5] Fix observability detection to use type-safe downcasting and remove unused business_metrics test --- aws/rust-runtime/Cargo.lock | 60 ++++++++++++ .../src/credentials_impl.rs | 4 +- .../aws-inlineable/src/aws_chunked.rs | 4 +- .../src/http_request_checksum.rs | 5 +- aws/rust-runtime/aws-runtime/Cargo.toml | 5 +- .../aws-runtime/src/endpoint_override.rs | 2 +- .../aws-runtime/src/env_config/section.rs | 12 +-- .../aws-runtime/src/env_config/source.rs | 2 +- .../src/observability_detection.rs | 92 ++++++++++++++----- .../aws-runtime/src/sdk_feature.rs | 4 + .../aws-runtime/src/user_agent/metrics.rs | 5 +- aws/rust-runtime/aws-sigv4/Cargo.toml | 2 +- .../src/http_request/canonical_request.rs | 4 +- .../aws-sigv4/src/http_request/test.rs | 8 +- aws/rust-runtime/aws-types/Cargo.toml | 2 +- aws/rust-runtime/aws-types/src/sdk_config.rs | 6 +- aws/sdk/integration-tests/s3/Cargo.toml | 2 + .../integration-tests/webassembly/Cargo.toml | 2 +- rust-runtime/Cargo.lock | 2 +- .../aws-smithy-observability-otel/Cargo.toml | 8 +- .../aws-smithy-observability-otel/src/lib.rs | 3 +- .../src/client/sdk_feature.rs | 1 + 22 files changed, 176 insertions(+), 59 deletions(-) diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index 481cd1831c9..172faae996b 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -293,6 +293,7 @@ dependencies = [ "regex-lite", "serde", "serde_json", + "serial_test", "tokio", "tracing", "tracing-subscriber", @@ -1093,6 +1094,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1100,6 +1116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1167,10 +1184,13 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -2282,6 +2302,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.28" @@ -2307,6 +2336,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "sec1" version = "0.3.0" @@ -2399,6 +2434,31 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/aws/rust-runtime/aws-credential-types/src/credentials_impl.rs b/aws/rust-runtime/aws-credential-types/src/credentials_impl.rs index eef3523b72f..8c2666869fb 100644 --- a/aws/rust-runtime/aws-credential-types/src/credentials_impl.rs +++ b/aws/rust-runtime/aws-credential-types/src/credentials_impl.rs @@ -431,7 +431,7 @@ mod test { "debug tester", ); assert_eq!( - format!("{:?}", creds), + format!("{creds:?}"), r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z" }"# ); @@ -445,7 +445,7 @@ mod test { .provider_name("debug tester") .build(); assert_eq!( - format!("{:?}", creds), + format!("{creds:?}"), r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z", account_id: "012345678901" }"# ); } diff --git a/aws/rust-runtime/aws-inlineable/src/aws_chunked.rs b/aws/rust-runtime/aws-inlineable/src/aws_chunked.rs index bb647f248f4..ec8dbc2de07 100644 --- a/aws/rust-runtime/aws-inlineable/src/aws_chunked.rs +++ b/aws/rust-runtime/aws-inlineable/src/aws_chunked.rs @@ -180,7 +180,7 @@ mod tests { let mut file = NamedTempFile::new().unwrap(); for i in 0..10000 { - let line = format!("This is a large file created for testing purposes {}", i); + let line = format!("This is a large file created for testing purposes {i}"); file.as_file_mut().write_all(line.as_bytes()).unwrap(); } @@ -302,7 +302,7 @@ mod tests { async fn streaming_body(path: impl AsRef) -> SdkBody { let file = path.as_ref(); ByteStream::read_from() - .path(&file) + .path(file) .build() .await .unwrap() diff --git a/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs b/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs index c3695840006..f010d98cf51 100644 --- a/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs +++ b/aws/rust-runtime/aws-inlineable/src/http_request_checksum.rs @@ -427,6 +427,7 @@ mod tests { use http_body::Body; use tempfile::NamedTempFile; + #[allow(clippy::type_complexity)] fn create_test_interceptor() -> RequestChecksumInterceptor< impl Fn(&Input) -> (Option, bool) + Send + Sync, impl Fn(&mut Request, &ConfigBag) -> Result + Send + Sync, @@ -449,7 +450,7 @@ mod tests { let mut crc32c_checksum = checksum_algorithm.into_impl(); for i in 0..10000 { - let line = format!("This is a large file created for testing purposes {}", i); + let line = format!("This is a large file created for testing purposes {i}"); file.as_file_mut().write_all(line.as_bytes()).unwrap(); crc32c_checksum.update(line.as_bytes()); } @@ -495,7 +496,7 @@ mod tests { body_data.extend_from_slice(&data.unwrap()) } let body_str = std::str::from_utf8(&body_data).unwrap(); - let expected = format!("This is a large file created for testing purposes 9999"); + let expected = "This is a large file created for testing purposes 9999".to_string(); assert!( body_str.ends_with(&expected), "expected '{body_str}' to end with '{expected}'" diff --git a/aws/rust-runtime/aws-runtime/Cargo.toml b/aws/rust-runtime/aws-runtime/Cargo.toml index ad0a1371ce5..e922b4a137b 100644 --- a/aws/rust-runtime/aws-runtime/Cargo.toml +++ b/aws/rust-runtime/aws-runtime/Cargo.toml @@ -22,7 +22,6 @@ aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } aws-smithy-observability = { path = "../../../rust-runtime/aws-smithy-observability" } -aws-smithy-observability-otel = { path = "../../../rust-runtime/aws-smithy-observability-otel" } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } @@ -39,6 +38,9 @@ regex-lite = { version = "0.1.5", optional = true } tracing = "0.1.40" uuid = { version = "1" } +[target.'cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))'.dependencies] +aws-smithy-observability-otel = { version = "0.1.3", path = "../../../rust-runtime/aws-smithy-observability-otel" } + [dev-dependencies] arbitrary = "1.3" aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] } @@ -52,6 +54,7 @@ futures-util = { version = "0.3.29", default-features = false } proptest = "1.2" serde = { version = "1", features = ["derive"]} serde_json = "1" +serial_test = "3" tokio = { version = "1.23.1", features = ["macros", "rt", "time"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tracing-test = "0.2.4" diff --git a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs index 6a1f41bedc4..33b30c6de10 100644 --- a/aws/rust-runtime/aws-runtime/src/endpoint_override.rs +++ b/aws/rust-runtime/aws-runtime/src/endpoint_override.rs @@ -153,7 +153,7 @@ mod tests { .load::() .cloned() .collect(); - assert_eq!(features.len(), 1, "Expected 1 feature, got: {:?}", features); + assert_eq!(features.len(), 1, "Expected 1 feature, got: {features:?}"); assert_eq!(features[0], AwsSdkFeature::EndpointOverride); } } diff --git a/aws/rust-runtime/aws-runtime/src/env_config/section.rs b/aws/rust-runtime/aws-runtime/src/env_config/section.rs index bb8c9015e26..6e7b39a7f06 100644 --- a/aws/rust-runtime/aws-runtime/src/env_config/section.rs +++ b/aws/rust-runtime/aws-runtime/src/env_config/section.rs @@ -292,7 +292,7 @@ mod test { let tests = fs::read_to_string("test-data/profile-parser-tests.json")?; let tests: ParserTests = serde_json::from_str(&tests)?; for (i, test) in tests.tests.into_iter().enumerate() { - eprintln!("test: {}", i); + eprintln!("test: {i}"); check(test); } Ok(()) @@ -432,22 +432,22 @@ mod test { } } (Err(msg), ParserOutput::ErrorContaining(substr)) => { - if format!("{}", msg).contains(substr) { + if format!("{msg}").contains(substr) { Ok(()) } else { - Err(format!("Expected {} to contain {}", msg, substr)) + Err(format!("Expected {msg} to contain {substr}")) } } (Ok(output), ParserOutput::ErrorContaining(err)) => Err(format!( "expected an error: {err} but parse succeeded:\n{output:#?}", )), (Err(err), ParserOutput::Config { .. }) => { - Err(format!("Expected to succeed but got: {}", err)) + Err(format!("Expected to succeed but got: {err}")) } }; if let Err(e) = res { - eprintln!("Test case failed: {:#?}", copy); - eprintln!("failure: {}", e); + eprintln!("Test case failed: {copy:#?}"); + eprintln!("failure: {e}"); panic!("test failed") } } diff --git a/aws/rust-runtime/aws-runtime/src/env_config/source.rs b/aws/rust-runtime/aws-runtime/src/env_config/source.rs index 8661b938266..0351234c7a6 100644 --- a/aws/rust-runtime/aws-runtime/src/env_config/source.rs +++ b/aws/rust-runtime/aws-runtime/src/env_config/source.rs @@ -245,7 +245,7 @@ mod tests { let tests = fs::read_to_string("test-data/file-location-tests.json")?; let tests: SourceTests = serde_json::from_str(&tests)?; for (i, test) in tests.tests.into_iter().enumerate() { - eprintln!("test: {}", i); + eprintln!("test: {i}"); check(test) .now_or_never() .expect("these futures should never poll"); diff --git a/aws/rust-runtime/aws-runtime/src/observability_detection.rs b/aws/rust-runtime/aws-runtime/src/observability_detection.rs index ae8a2cb87aa..8d3c21c0f0e 100644 --- a/aws/rust-runtime/aws-runtime/src/observability_detection.rs +++ b/aws/rust-runtime/aws-runtime/src/observability_detection.rs @@ -10,7 +10,11 @@ //! - [`crate::observability_detection::ObservabilityDetectionInterceptor`]: Detects observability features during //! request processing and tracks them for business metrics in the User-Agent header. +#[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] +use crate::sdk_feature::AwsSdkFeature; +#[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] use aws_smithy_observability_otel::meter::OtelMeterProvider; +#[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextRef; @@ -40,22 +44,37 @@ impl Intercept for ObservabilityDetectionInterceptor { &self, _context: &BeforeTransmitInterceptorContextRef<'_>, _runtime_components: &RuntimeComponents, - cfg: &mut ConfigBag, + _cfg: &mut ConfigBag, ) -> Result<(), BoxError> { - // Try to get the global telemetry provider - if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() { - // Use type-safe downcasting to detect OpenTelemetry meter provider - // This works with any ProvideMeter implementation and doesn't require - // implementation-specific boolean flags - if provider - .meter_provider() - .as_any() - .downcast_ref::() - .is_some() - { - // Track that observability metrics are enabled - cfg.interceptor_state() - .store_append(SmithySdkFeature::ObservabilityMetrics); + #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] + { + // Try to get the global telemetry provider + if let Ok(provider) = aws_smithy_observability::global::get_telemetry_provider() { + let meter_provider = provider.meter_provider(); + + // Check if this is an OpenTelemetry meter provider + let is_otel = meter_provider + .as_any() + .downcast_ref::() + .is_some(); + + // Check if this is a noop provider (we don't want to track noop) + let is_noop = meter_provider + .as_any() + .downcast_ref::() + .is_some(); + + if !is_noop { + // Track generic observability metrics (for any non-noop provider) + _cfg.interceptor_state() + .store_append(SmithySdkFeature::ObservabilityMetrics); + + // If it's specifically OpenTelemetry, track that too + if is_otel { + _cfg.interceptor_state() + .store_append(AwsSdkFeature::ObservabilityOtelMetrics); + } + } } } @@ -73,7 +92,9 @@ mod tests { use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_types::config_bag::ConfigBag; + #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] #[test] + #[serial_test::serial] fn test_detects_noop_provider() { let mut context = InterceptorContext::new(Input::doesnt_matter()); context.enter_serialization_phase(); @@ -93,15 +114,33 @@ mod tests { .read_before_signing(&ctx, &rc, &mut cfg) .unwrap(); - // Should not track any features for noop provider since it doesn't downcast to OtelMeterProvider - let smithy_features: Vec<_> = cfg.load::().collect(); - assert_eq!(smithy_features.len(), 0); + // Should not track any features for noop provider + let smithy_features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert_eq!( + smithy_features.len(), + 0, + "Should not track Smithy features for noop provider" + ); - let aws_features: Vec<_> = cfg.load::().collect(); - assert_eq!(aws_features.len(), 0); + let aws_features: Vec<_> = cfg + .interceptor_state() + .load::() + .cloned() + .collect(); + assert_eq!( + aws_features.len(), + 0, + "Should not track AWS features for noop provider" + ); } + #[cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] #[test] + #[serial_test::serial] fn test_custom_provider_not_detected_as_otel() { use aws_smithy_observability::meter::{Meter, ProvideMeter}; use aws_smithy_observability::noop::NoopMeterProvider; @@ -150,24 +189,27 @@ mod tests { .read_before_signing(&ctx, &rc, &mut cfg) .unwrap(); - // Should NOT track any features for custom provider since it doesn't downcast to OtelMeterProvider - // The new implementation only emits metrics when OTel is detected + // Should track generic observability metrics for custom provider let smithy_features: Vec<_> = cfg .interceptor_state() .load::() .cloned() .collect(); assert!( - !smithy_features.iter().any(|f| *f == SmithySdkFeature::ObservabilityMetrics), - "Should not detect custom provider as having observability metrics (only OTel is tracked)" + smithy_features.contains(&SmithySdkFeature::ObservabilityMetrics), + "Should detect custom provider as having observability metrics" ); - // Verify no AWS-specific features are tracked for custom provider + // Should NOT track AWS-specific observability metrics for custom provider let aws_features: Vec<_> = cfg .interceptor_state() .load::() .cloned() .collect(); + assert!( + !aws_features.contains(&AwsSdkFeature::ObservabilityOtelMetrics), + "Should NOT track OTel-specific metrics for custom provider" + ); assert_eq!( aws_features.len(), 0, diff --git a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs index 7c1d437627b..20713ff689c 100644 --- a/aws/rust-runtime/aws-runtime/src/sdk_feature.rs +++ b/aws/rust-runtime/aws-runtime/src/sdk_feature.rs @@ -26,6 +26,10 @@ pub enum AwsSdkFeature { SsoLoginAuth, /// An operation called using a user provided endpoint URL EndpointOverride, + /// An operation called with OpenTelemetry tracing integration enabled + ObservabilityOtelTracing, + /// An operation called with OpenTelemetry metrics integration enabled + ObservabilityOtelMetrics, } impl Storable for AwsSdkFeature { diff --git a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs index 66e6081f0ca..08f629615ef 100644 --- a/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs +++ b/aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs @@ -202,6 +202,7 @@ impl ProvideBusinessMetric for SmithySdkFeature { FlexibleChecksumsResWhenRequired => { Some(BusinessMetric::FlexibleChecksumsResWhenRequired) } + ObservabilityTracing => Some(BusinessMetric::ObservabilityTracing), ObservabilityMetrics => Some(BusinessMetric::ObservabilityMetrics), otherwise => { // This may occur if a customer upgrades only the `aws-smithy-runtime-api` crate @@ -228,6 +229,8 @@ impl ProvideBusinessMetric for AwsSdkFeature { SsoLoginDevice => Some(BusinessMetric::SsoLoginDevice), SsoLoginAuth => Some(BusinessMetric::SsoLoginAuth), EndpointOverride => Some(BusinessMetric::EndpointOverride), + ObservabilityOtelTracing => Some(BusinessMetric::ObservabilityOtelTracing), + ObservabilityOtelMetrics => Some(BusinessMetric::ObservabilityOtelMetrics), } } } @@ -342,7 +345,7 @@ mod tests { impl Display for BusinessMetric { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str( - &format!("{:?}", self) + &format!("{self:?}") .as_str() .from_case(Case::Pascal) .with_boundaries(&[Boundary::DigitUpper, Boundary::LowerUpper]) diff --git a/aws/rust-runtime/aws-sigv4/Cargo.toml b/aws/rust-runtime/aws-sigv4/Cargo.toml index de172669820..2c47b135138 100644 --- a/aws/rust-runtime/aws-sigv4/Cargo.toml +++ b/aws/rust-runtime/aws-sigv4/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-sigv4" -version = "1.3.6" +version = "1.3.8" authors = ["AWS Rust SDK Team ", "David Barsky "] description = "SigV4 signer for HTTP requests and Event Stream messages." edition = "2021" diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs b/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs index b53f33fb9cf..d875afe265c 100644 --- a/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs +++ b/aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs @@ -881,7 +881,7 @@ mod tests { let creq = CanonicalRequest::from(&req, &signing_params).unwrap(); let expected = test.canonical_request(SignatureLocation::Headers); - let actual = format!("{}", creq); + let actual = format!("{creq}"); assert_eq!(actual, expected); } @@ -894,7 +894,7 @@ mod tests { let signing_params = signing_params(&identity, SigningSettings::default()); let creq = CanonicalRequest::from(&req, &signing_params).unwrap(); let expected = test.canonical_request(SignatureLocation::Headers); - let actual = format!("{}", creq); + let actual = format!("{creq}"); assert_eq!(actual, expected); } diff --git a/aws/rust-runtime/aws-sigv4/src/http_request/test.rs b/aws/rust-runtime/aws-sigv4/src/http_request/test.rs index 86eec201508..cacf4b351ac 100644 --- a/aws/rust-runtime/aws-sigv4/src/http_request/test.rs +++ b/aws/rust-runtime/aws-sigv4/src/http_request/test.rs @@ -111,7 +111,7 @@ impl SigningSuiteTest { fn test_parsed_request(path: &str) -> TestRequest { match parse_request(read(path).as_bytes()) { Ok(parsed) => parsed, - Err(err) => panic!("Failed to parse {}: {}", path, err), + Err(err) => panic!("Failed to parse {path}: {err}"), } } @@ -428,14 +428,14 @@ pub(crate) mod v4a { } fn read(path: &str) -> String { - println!("Loading `{}` for test case...", path); + println!("Loading `{path}` for test case..."); let v = { match std::fs::read_to_string(path) { // This replacement is necessary for tests to pass on Windows, as reading the // test snapshots from the file system results in CRLF line endings being inserted. Ok(value) => value.replace("\r\n", "\n"), Err(err) => { - panic!("failed to load test case `{}`: {}", path, err); + panic!("failed to load test case `{path}`: {err}"); } } }; @@ -487,7 +487,7 @@ impl> From> for TestRequest { .values() .find(|h| std::str::from_utf8(h.as_bytes()).is_err()); if let Some(invalid) = invalid { - panic!("invalid header: {:?}", invalid); + panic!("invalid header: {invalid:?}"); } Self { uri: value.uri().to_string(), diff --git a/aws/rust-runtime/aws-types/Cargo.toml b/aws/rust-runtime/aws-types/Cargo.toml index 82d530b7b21..09a7d51f035 100644 --- a/aws/rust-runtime/aws-types/Cargo.toml +++ b/aws/rust-runtime/aws-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-types" -version = "1.3.10" +version = "1.3.11" authors = ["AWS Rust SDK Team ", "Russell Cohen "] description = "Cross-service types for the AWS SDK." edition = "2021" diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index fc1f7080a53..5b9674224af 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -442,7 +442,7 @@ impl Builder { /// /// # Examples /// Disabling identity caching: - /// ```rust + /// ```rust,ignore /// # use aws_types::SdkConfig; /// use aws_smithy_runtime::client::identity::IdentityCache; /// let config = SdkConfig::builder() @@ -450,7 +450,7 @@ impl Builder { /// .build(); /// ``` /// Changing settings on the default cache implementation: - /// ```rust + /// ```rust,ignore /// # use aws_types::SdkConfig; /// use aws_smithy_runtime::client::identity::IdentityCache; /// use std::time::Duration; @@ -475,7 +475,7 @@ impl Builder { /// expires. /// /// # Examples - /// ```rust + /// ```rust,ignore /// # use aws_types::SdkConfig; /// use aws_smithy_runtime::client::identity::IdentityCache; /// diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index a1c3139cc43..d88a02a627e 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -29,6 +29,8 @@ aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-smithy-http-client = { path = "../../build/aws-sdk/sdk/aws-smithy-http-client", features = ["default-client", "rustls-ring", "test-util", "wire-mock"] } aws-smithy-mocks = { path = "../../build/aws-sdk/sdk/aws-smithy-mocks" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } +opentelemetry = "0.27" +opentelemetry_sdk = "0.27" bytes = "1" bytes-utils = "0.1.2" fastrand = "2.3.0" diff --git a/aws/sdk/integration-tests/webassembly/Cargo.toml b/aws/sdk/integration-tests/webassembly/Cargo.toml index 658288fd404..782430b34ba 100644 --- a/aws/sdk/integration-tests/webassembly/Cargo.toml +++ b/aws/sdk/integration-tests/webassembly/Cargo.toml @@ -33,7 +33,7 @@ aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-ap aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-smithy-wasm = { path = "../../build/aws-sdk/sdk/aws-smithy-wasm" } http = "0.2.9" -tokio = { version = "1.32.0", features = ["macros", "rt"] } +tokio = { version = "1.32.0", features = ["macros", "rt", "sync", "time"], default-features = false } # getrandom is a transitive dependency, but requires the wasm_js feature to compile for wasm # also requires a compiler flag which is set in .cargo/config.toml # https://docs.rs/getrandom/0.3.3/getrandom/#webassembly-support diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index 402580ef095..2f6735ad4ce 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -539,7 +539,7 @@ dependencies = [ [[package]] name = "aws-smithy-mocks" -version = "0.2.0" +version = "0.2.1" dependencies = [ "aws-smithy-async", "aws-smithy-http-client", diff --git a/rust-runtime/aws-smithy-observability-otel/Cargo.toml b/rust-runtime/aws-smithy-observability-otel/Cargo.toml index 5eb9007ae34..dfa6fee4504 100644 --- a/rust-runtime/aws-smithy-observability-otel/Cargo.toml +++ b/rust-runtime/aws-smithy-observability-otel/Cargo.toml @@ -11,16 +11,16 @@ repository = "https://github.com/awslabs/smithy-rs" [dependencies] aws-smithy-observability = { path = "../aws-smithy-observability" } -opentelemetry = {version = "0.26.0", features = ["metrics"]} +opentelemetry = {version = "0.27.0", features = ["metrics"]} # The following dependencies are transitive and pinned for build # compatability purposes value-bag = "1.10.0" async-global-executor = "2.4.1" async-task = "=4.7.1" -# This crate cannot be used on powerpc -[target.'cfg(not(target_arch = "powerpc"))'.dependencies] -opentelemetry_sdk = {version = "0.26.0", features = ["metrics", "testing"]} +# This crate cannot be used on powerpc or WASM (opentelemetry_sdk depends on async-std which doesn't support WASM) +[target.'cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))'.dependencies] +opentelemetry_sdk = {version = "0.27.0", features = ["metrics", "testing"]} [dev-dependencies] tokio = { version = "1.23.1" } diff --git a/rust-runtime/aws-smithy-observability-otel/src/lib.rs b/rust-runtime/aws-smithy-observability-otel/src/lib.rs index a84975f8c27..44ee0dd2464 100644 --- a/rust-runtime/aws-smithy-observability-otel/src/lib.rs +++ b/rust-runtime/aws-smithy-observability-otel/src/lib.rs @@ -13,7 +13,8 @@ rust_2018_idioms )] // The `opentelemetry_sdk` crate uses std::sync::atomic::{AtomicI64, AtomicU64} which are not available on powerpc -#![cfg(not(target_arch = "powerpc"))] +// Additionally, opentelemetry_sdk depends on async-std which is not compatible with WASM targets +#![cfg(all(not(target_arch = "powerpc"), not(target_family = "wasm")))] //! Smithy Observability OpenTelemetry //TODO(smithyobservability): once we have finalized everything and integrated metrics with our runtime diff --git a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs index cc02a266825..2904c3f5541 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/sdk_feature.rs @@ -23,6 +23,7 @@ pub enum SmithySdkFeature { FlexibleChecksumsReqWhenRequired, FlexibleChecksumsResWhenSupported, FlexibleChecksumsResWhenRequired, + ObservabilityTracing, ObservabilityMetrics, }