Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
740742f
Track observability business metric (metric 7)
vcjana Nov 24, 2025
30648d2
Fix formatting, bump codegen version to 0.1.7, and add observability …
vcjana Nov 24, 2025
3e09af7
Move ObservabilityOtelMetrics to SmithySdkFeature and add default as_…
vcjana Nov 26, 2025
02e7b83
Make as_any() required method for trait objects
vcjana Nov 26, 2025
869532b
Fix opentelemetry 0.26.0 dependencies and test compatibility
vcjana Nov 28, 2025
e7f0f5c
fix(observability): Add provider_name() method and hide noop module f…
vcjana Dec 1, 2025
1fcff09
Apply rustfmt and bump aws-smithy-observability to 0.1.6
vcjana Dec 1, 2025
6d6de50
Merge remote-tracking branch 'origin/main' into observability-metrics…
vcjana Dec 1, 2025
fd8f587
Remove trailing whitespace
vcjana Dec 1, 2025
655fdb3
Merge branch 'main' into observability-metrics-implementation
landonxjames Dec 2, 2025
595e884
Fix as_any trait and address review comments
vcjana Dec 2, 2025
703af49
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 2, 2025
389aefe
Allow dead_code in test utils
vcjana Dec 2, 2025
f8aa750
Bump version to 0.2.0
vcjana Dec 2, 2025
ee5e070
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 3, 2025
efc995e
Bump aws-smithy-observability version to 0.3.0
vcjana Dec 3, 2025
85a14fd
Bump aws-smithy-observability version to 0.2.0
vcjana Dec 4, 2025
7f0b853
Add default impl for as_any()
vcjana Dec 4, 2025
20f6231
Fixing the CI issues
vcjana Dec 4, 2025
07ad5f1
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 5, 2025
810108d
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 8, 2025
dd65798
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 9, 2025
d11fae0
Bump codegen and runtime versions for CI
vcjana Dec 10, 2025
af3665b
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 15, 2025
401e807
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 16, 2025
1c3c221
Merge branch 'main' into observability-metrics-implementation
vcjana Dec 17, 2025
88252d1
Cleanup of as_any()
vcjana Dec 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changelog/observability-metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
applies_to: ["client"]
authors: ["vcjana"]
references: []
breaking: false
new_feature: false
bug_fix: false
---
Add support for tracking observability business metrics (OBSERVABILITY_TRACING, OBSERVABILITY_OTEL_TRACING, OBSERVABILITY_OTEL_METRICS) in User-Agent headers when telemetry providers are configured.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ val DECORATORS: List<ClientCodegenDecorator> =
CredentialsProviderDecorator(),
RegionDecorator(),
RequireEndpointRules(),
ObservabilityMetricDecorator(),
EndpointOverrideMetricDecorator(),
UserAgentDecorator(),
SigV4AuthDecorator(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType

/**
* Decorator that tracks observability business metrics when tracing/metrics providers are configured.
*/
class ObservabilityMetricDecorator : ClientCodegenDecorator {
override val name: String = "ObservabilityMetric"
override val order: Byte = 0

override fun serviceRuntimePluginCustomizations(
codegenContext: ClientCodegenContext,
baseCustomizations: List<ServiceRuntimePluginCustomization>,
): List<ServiceRuntimePluginCustomization> =
baseCustomizations + listOf(ObservabilityFeatureTrackerInterceptor(codegenContext))
}

private class ObservabilityFeatureTrackerInterceptor(private val codegenContext: ClientCodegenContext) :
ServiceRuntimePluginCustomization() {
override fun section(section: ServiceRuntimePluginSection) =
writable {
if (section is ServiceRuntimePluginSection.RegisterRuntimeComponents) {
section.registerInterceptor(this) {
val runtimeConfig = codegenContext.runtimeConfig
rustTemplate(
"#{Interceptor}",
"Interceptor" to
RuntimeType.forInlineDependency(
InlineAwsDependency.forRustFile(
"observability_feature",
Visibility.PRIVATE,
CargoDependency.smithyObservability(runtimeConfig),
),
).resolve("ObservabilityFeatureTrackerInterceptor"),
)
}
}
}
}
7 changes: 4 additions & 3 deletions aws/rust-runtime/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aws/rust-runtime/aws-inlineable/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ aws-types = { path = "../aws-types" }
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async", features = ["rt-tokio"] }
aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" }
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", features = ["http-body-0-4-x"] }
Expand Down
4 changes: 4 additions & 0 deletions aws/rust-runtime/aws-inlineable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#[allow(dead_code)]
pub mod account_id_endpoint;

/// Supporting code for tracking observability features (tracing/metrics).
#[allow(dead_code)]
pub mod observability_feature;

/// Supporting code for the aws-chunked content encoding.
pub mod aws_chunked;

Expand Down
40 changes: 40 additions & 0 deletions aws/rust-runtime/aws-inlineable/src/observability_feature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_smithy_runtime::client::sdk_feature::SmithySdkFeature;
use aws_smithy_runtime_api::{
box_error::BoxError,
client::interceptors::{context::BeforeSerializationInterceptorContextRef, Intercept},
};
use aws_smithy_types::config_bag::ConfigBag;

// Interceptor that tracks Smithy SDK features for observability (tracing/metrics).
#[derive(Debug, Default)]
pub(crate) struct ObservabilityFeatureTrackerInterceptor;

impl Intercept for ObservabilityFeatureTrackerInterceptor {
fn name(&self) -> &'static str {
"ObservabilityFeatureTrackerInterceptor"
}

fn read_before_execution(
&self,
_context: &BeforeSerializationInterceptorContextRef<'_>,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
// Check if an OpenTelemetry meter provider is configured via the global provider
if let Ok(telemetry_provider) = aws_smithy_observability::global::get_telemetry_provider() {
let meter_provider = telemetry_provider.meter_provider();

// Use provider_name() to detect OpenTelemetry without importing the otel crate.
if meter_provider.provider_name() == "AwsSmithyObservabilityOtelProvider" {
cfg.interceptor_state()
.store_append(SmithySdkFeature::ObservabilityOtelMetrics);
}
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion aws/rust-runtime/aws-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-runtime"
version = "1.5.17"
version = "1.5.18"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
description = "Runtime support code for the AWS SDK. This crate isn't intended to be used directly."
edition = "2021"
Expand Down
1 change: 1 addition & 0 deletions aws/rust-runtime/aws-runtime/src/user_agent/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ impl ProvideBusinessMetric for SmithySdkFeature {
FlexibleChecksumsResWhenRequired => {
Some(BusinessMetric::FlexibleChecksumsResWhenRequired)
}
ObservabilityOtelMetrics => Some(BusinessMetric::ObservabilityOtelMetrics),
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`
Expand Down
1 change: 1 addition & 0 deletions aws/sdk/integration-tests/telemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ publish = false

[dev-dependencies]
aws-config = { path = "../../build/aws-sdk/sdk/aws-config", features = ["test-util", "behavior-version-latest"] }
aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime", features = ["test-util"] }
aws-sdk-dynamodb = { path = "../../build/aws-sdk/sdk/dynamodb", features = ["test-util", "behavior-version-latest"] }
aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", features = ["test-util", "behavior-version-latest"] }
aws-smithy-observability = { path = "../../build/aws-sdk/sdk/aws-smithy-observability" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_config::Region;
use aws_runtime::user_agent::test_util::{
assert_ua_contains_metric_values, assert_ua_does_not_contain_metric_values,
};
use aws_sdk_s3::config::{Credentials, SharedCredentialsProvider};
use aws_smithy_observability::TelemetryProvider;
use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient};
use aws_smithy_types::body::SdkBody;
use serial_test::serial;
use utils::init_metrics;

mod utils;

// Note: These tests are written with a multi-threaded runtime since OTel requires that to work
// and they are all run serially since they touch global state

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
async fn observability_otel_metrics_feature_tracked_in_user_agent() {
let (meter_provider, _exporter) = init_metrics();

// Create a replay client to capture the actual HTTP request
let http_client = StaticReplayClient::new(vec![ReplayEvent::new(
http::Request::builder().body(SdkBody::empty()).unwrap(),
http::Response::builder().body(SdkBody::empty()).unwrap(),
)]);

let config = aws_config::SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.region(Region::new("us-east-1"))
.http_client(http_client.clone())
.build();

let s3_client = aws_sdk_s3::Client::new(&config);
let _ = s3_client
.get_object()
.bucket("test-bucket")
.key("test.txt")
.send()
.await;

// Get the actual HTTP request that was made
let requests = http_client.actual_requests();
let last_request = requests.last().expect("should have made a request");

let user_agent = last_request
.headers()
.get("x-amz-user-agent")
.expect("should have user-agent header");

// Should contain OBSERVABILITY_OTEL_METRICS metric (value "7")
assert_ua_contains_metric_values(user_agent, &["7"]);

meter_provider.flush().unwrap();

// Reset to noop for other tests
aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop()).unwrap();
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
async fn noop_provider_does_not_track_observability_metrics() {
// Reset to noop provider
aws_smithy_observability::global::set_telemetry_provider(TelemetryProvider::noop()).unwrap();

// Create a replay client to capture the actual HTTP request
let http_client = StaticReplayClient::new(vec![ReplayEvent::new(
http::Request::builder().body(SdkBody::empty()).unwrap(),
http::Response::builder().body(SdkBody::empty()).unwrap(),
)]);

let config = aws_config::SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.region(Region::new("us-east-1"))
.http_client(http_client.clone())
.build();

let s3_client = aws_sdk_s3::Client::new(&config);
let _ = s3_client
.get_object()
.bucket("test-bucket")
.key("test.txt")
.send()
.await;

// Get the actual HTTP request that was made
let requests = http_client.actual_requests();
let last_request = requests.last().expect("should have made a request");

let user_agent = last_request
.headers()
.get("x-amz-user-agent")
.expect("should have user-agent header");

// Should NOT contain OBSERVABILITY_OTEL_METRICS metric when using noop provider
assert_ua_does_not_contain_metric_values(user_agent, &["7"]);
}
4 changes: 4 additions & 0 deletions aws/sdk/integration-tests/telemetry/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(crate) fn init_metrics() -> (Arc<OtelMeterProvider>, InMemoryMetricsExporter
(sdk_ref, exporter)
}

#[allow(dead_code)]
pub(crate) fn new_replay_client(num_requests: usize, with_retry: bool) -> StaticReplayClient {
let mut events = Vec::with_capacity(num_requests);
let mut start = 0;
Expand Down Expand Up @@ -95,6 +96,7 @@ pub(crate) fn extract_metric_attributes<'a>(
.collect()
}

#[allow(dead_code)]
pub(crate) async fn make_s3_call(config: &SdkConfig) {
let s3_client = aws_sdk_s3::Client::new(config);
let _ = s3_client
Expand All @@ -105,6 +107,7 @@ pub(crate) async fn make_s3_call(config: &SdkConfig) {
.await;
}

#[allow(dead_code)]
pub(crate) async fn make_ddb_call(config: &SdkConfig) {
let ddb_client = aws_sdk_dynamodb::Client::new(&config);
let _ = ddb_client
Expand All @@ -115,6 +118,7 @@ pub(crate) async fn make_ddb_call(config: &SdkConfig) {
.await;
}

#[allow(dead_code)]
pub(crate) fn make_config(with_retry: bool) -> SdkConfig {
SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,11 @@ data class CargoDependency(
fun smithyMocks(runtimeConfig: RuntimeConfig) =
runtimeConfig.smithyRuntimeCrate("smithy-mocks", scope = DependencyScope.Dev)

fun smithyObservability(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-observability")

fun smithyObservabilityOtel(runtimeConfig: RuntimeConfig) =
runtimeConfig.smithyRuntimeCrate("smithy-observability-otel")

// behind feature-gate
val Serde =
CargoDependency("serde", CratesIo("1.0"), features = setOf("derive"), scope = DependencyScope.CfgUnstable)
Expand Down
2 changes: 1 addition & 1 deletion codegen-server-test/integration-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ allowLocalDeps=false
# Avoid registering dependencies/plugins/tasks that are only used for testing purposes
isTestingEnabled=true
# codegen publication version
codegenVersion=0.1.8
codegenVersion=0.1.9
6 changes: 3 additions & 3 deletions rust-runtime/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust-runtime/aws-smithy-observability-otel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-smithy-observability-otel"
version = "0.1.3"
version = "0.1.4"
authors = [
"AWS Rust SDK Team <aws-sdk-rust@amazon.com>",
]
Expand Down
4 changes: 4 additions & 0 deletions rust-runtime/aws-smithy-observability-otel/src/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 provider_name(&self) -> &'static str {
"AwsSmithyObservabilityOtelProvider"
}
}

#[cfg(test)]
Expand Down
Loading
Loading