Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4076048
Add DiagnosticsHandler chain to Cosmos SDK (WS2)
NaluTripician Jul 16, 2026
5f85f21
Bound Cosmos diagnostics under retry storms
NaluTripician Jul 16, 2026
06bcb04
Add OpenTelemetry metrics handler for Cosmos (WS3)
NaluTripician Jul 16, 2026
8659a5d
Add Cosmos tail-sampled tracing and sampling log
NaluTripician Jul 16, 2026
2a745d7
Merge remote-tracking branch 'upstream/main' into nalutripician/cosmo…
NaluTripician Jul 16, 2026
22640d1
Merge remote-tracking branch 'origin/nalutripician/cosmos-obs-ws6-bou…
NaluTripician Jul 16, 2026
618b238
Merge remote-tracking branch 'origin/nalutripician/cosmos-obs-ws3-met…
NaluTripician Jul 16, 2026
308dac3
Merge WS3 metrics, WS45 emission, WS6 bounding into observability
NaluTripician Jul 16, 2026
bb1dade
Wire op-scope context to diagnostics handlers (WS8)
NaluTripician Jul 16, 2026
a872e5a
Fold corrected Cosmos diagnostics contract doc into PR-OBS
NaluTripician Jul 16, 2026
b445087
Address review comments on Cosmos observability
NaluTripician Jul 16, 2026
ef64112
cosmos: mark active_instance.count metric as deferred in contract
NaluTripician Jul 17, 2026
6360d1f
cosmos: fix rustdoc CI + address second Copilot review round
NaluTripician Jul 17, 2026
149aee6
Merge upstream/main into cosmos-observability
NaluTripician Jul 21, 2026
5849ac7
Merge branch 'main' into nalutripician/cosmos-observability
NaluTripician Jul 21, 2026
74c29b7
Address review feedback on Cosmos observability layer
NaluTripician Jul 21, 2026
4d0216e
Address maintainer review nits on observability layer
NaluTripician Jul 21, 2026
1f2a3d1
Add handler-propagation e2e tests; fix metrics meter doc
NaluTripician Jul 21, 2026
328027f
Address re-review: bounded truncation, effective status, DTX seam
NaluTripician Jul 21, 2026
50e1fd8
Harden rate limiter, anchor span end, fix doc link
NaluTripician Jul 21, 2026
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions sdk/cosmos/.cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"firewalled",
"fixdate",
"flamegraph",
"flatbuffer",
"fmix",
"fract",
"francecentral",
Expand Down Expand Up @@ -175,6 +176,7 @@
"MEMORYSTATUSEX",
"Mgmt",
"malloc",
"materializer",
"mmap",
"moka",
"mpmc",
Expand Down Expand Up @@ -220,6 +222,7 @@
"pgcosmos",
"PIDS",
"Pigw",
"pingpong",
"pinning",
"Pkrange",
"pkranges",
Expand Down Expand Up @@ -256,12 +259,14 @@
"rhost",
"RNTBD",
"rgby",
"rollup",
"roundtripped",
"roundtrips",
"RTLD",
"RUPM",
"rwcache",
"sbyte",
"semconv",
"serviceunavailable",
"serviceversion",
"sess",
Expand Down Expand Up @@ -307,6 +312,7 @@
"unavail",
"uncollapsed",
"uncontended",
"underspecified",
"undrained",
"unfaulted",
"ungoverned",
Expand Down Expand Up @@ -345,6 +351,8 @@
"westus",
"widenings",
"winget",
"workstream",
"workstreams",
"writability",
"xorshift",
"xpart",
Expand Down
4 changes: 4 additions & 0 deletions sdk/cosmos/azure_data_cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Features Added

- Added a pluggable client-side diagnostics emission layer — the `DiagnosticsHandler` trait and ordered `DiagnosticsHandlerChain` (registered via `CosmosClientBuilder::with_diagnostics_handler`) — invoked once per operation (singleton and paginated, on success and failure) with the completed `DiagnosticsContext` plus an SDK-supplied `CosmosOperationContext`; the empty default chain is a zero-overhead no-op. ([#4789](https://github.com/Azure/azure-sdk-for-rust/pull/4789))
- Added the `otel_metrics`-gated `CosmosMetricsHandler` (with `MetricsOptions`), emitting the stable `db.client.operation.duration` histogram plus opt-in development metrics (`operation.request_charge`, `response.returned_rows`); a no-op when no meter provider is registered. ([#4789](https://github.com/Azure/azure-sdk-for-rust/pull/4789))
- Added tail-sampled emission handlers — `SamplingLogHandler` (rate-limited `tracing` line) and the `otel_tracing`-gated `CosmosTracingHandler` (backdated span tree) — that emit only for operations which fail or breach a configurable `DiagnosticsThresholds`. ([#4789](https://github.com/Azure/azure-sdk-for-rust/pull/4789))

### Breaking Changes

### Bugs Fixed
Expand Down
13 changes: 13 additions & 0 deletions sdk/cosmos/azure_data_cosmos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ azure_core = { workspace = true, default-features = false }
azure_data_cosmos_driver = { version = "0.7.0", path = "../azure_data_cosmos_driver", default-features = false }
base64.workspace = true
futures.workspace = true
opentelemetry = { workspace = true, optional = true, features = ["metrics"] }
pin-project.workspace = true
reqwest = { workspace = true, optional = true }
serde.workspace = true
Expand Down Expand Up @@ -51,6 +52,10 @@ azure_data_cosmos_driver = { path = "../azure_data_cosmos_driver", default-featu
] }
azure_identity.workspace = true
clap.workspace = true
# Enabled here (dev-only) so the metrics and tracing handler tests can drive
# in-memory OpenTelemetry exporters. `testing` provides `InMemoryMetricExporter`
# and `InMemorySpanExporter`.
opentelemetry_sdk = { workspace = true, features = ["testing"] }
reqwest = { workspace = true, features = ["http2"] }
serde = { workspace = true, features = ["derive"] }
serde_bytes.workspace = true
Expand Down Expand Up @@ -79,11 +84,17 @@ rustls = ["reqwest", "azure_data_cosmos_driver/rustls", "__tls"]
native_tls = ["reqwest", "azure_data_cosmos_driver/native_tls", "__tls"]
key_auth = [
] # Enables support for key-based authentication (Primary Keys and Resource Tokens)
otel_tracing = [
"dep:opentelemetry",
] # Enables the OpenTelemetry tracing diagnostics handler (off by default)
hmac_rust = ["azure_core/hmac_rust"]
hmac_openssl = ["azure_core/hmac_openssl"]
fault_injection = [
"azure_data_cosmos_driver/fault_injection",
] # Enables support for fault injection testing
otel_metrics = [
"dep:opentelemetry",
] # Enables the OpenTelemetry metrics handler (`CosmosMetricsHandler`). Off by default: with the feature disabled the metrics module is not compiled, so there is zero cost.
preview_dtx = [
"azure_data_cosmos_driver/preview_dtx",
] # Enables preview Distributed Transaction APIs. Disabled by default and not production-ready.
Expand All @@ -100,6 +111,8 @@ features = [
"hmac_rust",
"key_auth",
"native_tls",
"otel_metrics",
"otel_tracing",
"rustls",
]

Expand Down
92 changes: 60 additions & 32 deletions sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::{
clients::{offers_client, ClientContext},
diagnostics::CosmosOperationContext,
feed::{ChangeFeedPageIterator, FeedRange, FeedScope, QueryItemIterator},
models::TransactionalBatch,
models::{BatchResponse, ChangeFeedItem, ItemResponse, ResourceResponse},
Expand Down Expand Up @@ -62,6 +63,16 @@ impl ContainerClient {
})
}

/// Builds the SDK-side [`CosmosOperationContext`] for this container's
/// operations, carrying the operation name plus the database and container
/// identity the driver context does not know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprised the driver doesn't have this context. Doesn't it have these on CosmosOperation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has some of it. CosmosOperation carries the ContainerReference (database + container names) and an OperationType / ResourceType, but two gaps make CosmosOperationContext worthwhile: (1) the handler chain receives the finalized DiagnosticsContext, not the CosmosOperation — the driver's context deliberately doesn't embed the caller-facing identity; and (2) the semconv db.operation.name (read_item vs query_items) is an SDK-level name, finer-grained than the driver's coarse OperationType. The alternative is threading the identity onto the driver's DiagnosticsContext, but that pushes SDK naming concerns into the driver. Happy to go that way if you'd prefer.

fn operation_context(&self, operation_name: &'static str) -> CosmosOperationContext {
CosmosOperationContext::new()
.with_operation_name(operation_name)
.with_database_name(self.container_ref.database_name().to_string())
.with_container_name(self.container_ref.name().to_string())
}

/// Reads the properties of the container.
///
/// # Arguments
Expand All @@ -85,14 +96,15 @@ impl ContainerClient {
let options = options.unwrap_or_default();
let operation = CosmosOperation::read_container(self.container_ref.clone());

let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("read_container"))?,
))
}

Expand Down Expand Up @@ -138,14 +150,16 @@ impl ContainerClient {
operation_options.content_response_on_write =
Some(azure_data_cosmos_driver::options::ContentResponseOnWrite::Enabled);

let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, operation_options)
.await?;
.await;

Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context.complete_result(driver_result, || {
self.operation_context("replace_container")
})?,
))
}

Expand All @@ -161,10 +175,11 @@ impl ContainerClient {
) -> crate::Result<Option<ThroughputProperties>> {
let options = options.unwrap_or_default();
offers_client::find_offer(
&self.context.driver,
&self.context,
self.container_ref.account(),
self.container_ref.rid(),
options.operation,
self.operation_context("read_throughput"),
)
.await
}
Expand Down Expand Up @@ -202,11 +217,12 @@ impl ContainerClient {
let options = options.unwrap_or_default();

offers_client::begin_replace(
self.context.driver.clone(),
self.context.clone(),
self.container_ref.account().clone(),
self.container_ref.rid(),
throughput,
options.operation,
self.operation_context("replace_throughput"),
)
.await
}
Expand All @@ -224,14 +240,15 @@ impl ContainerClient {
let options = options.unwrap_or_default();
let operation = CosmosOperation::delete_container(self.container_ref.clone());

let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

Ok(ResourceResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("delete_container"))?,
))
}

Expand Down Expand Up @@ -322,15 +339,16 @@ impl ContainerClient {
let operation = apply_item_options(operation, options.session_token, options.precondition);

// Execute through the driver.
let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

// Bridge the driver response to the SDK response type.
Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("create_item"))?,
))
}

Expand Down Expand Up @@ -420,15 +438,16 @@ impl ContainerClient {
let operation = apply_item_options(operation, options.session_token, options.precondition);

// Execute through the driver.
let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

// Bridge the driver response to the SDK response type.
Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("replace_item"))?,
))
}

Expand Down Expand Up @@ -527,14 +546,15 @@ impl ContainerClient {
// session token.
let operation = apply_item_options(operation, options.session_token, None);

let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("patch_item"))?,
))
}

Expand Down Expand Up @@ -628,15 +648,16 @@ impl ContainerClient {
let operation = apply_item_options(operation, options.session_token, options.precondition);

// Execute through the driver.
let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

// Bridge the driver response to the SDK response type.
Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("upsert_item"))?,
))
}

Expand Down Expand Up @@ -688,15 +709,16 @@ impl ContainerClient {
let operation = apply_item_options(operation, options.session_token, options.precondition);

// Execute through the driver.
let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

// Bridge the driver response to the SDK response type.
Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("read_item"))?,
))
}

Expand Down Expand Up @@ -740,15 +762,16 @@ impl ContainerClient {
let operation = apply_item_options(operation, options.session_token, options.precondition);

// Execute through the driver.
let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

// Bridge the driver response to the SDK response type.
Ok(ItemResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("delete_item"))?,
))
}

Expand Down Expand Up @@ -858,6 +881,8 @@ impl ContainerClient {
Some(self.container_ref.clone()),
plan,
options.operation,
self.context.diagnostics_handlers.clone(),
Comment thread
NaluTripician marked this conversation as resolved.
self.operation_context("query_items"),
))
}

Expand Down Expand Up @@ -951,6 +976,8 @@ impl ContainerClient {
Some(self.container_ref.clone()),
plan,
options.operation,
self.context.diagnostics_handlers.clone(),
Comment thread
NaluTripician marked this conversation as resolved.
self.operation_context("query_change_feed"),
))
}

Expand Down Expand Up @@ -1008,14 +1035,15 @@ impl ContainerClient {
CosmosOperation::batch(self.container_ref.clone(), driver_pk).with_body(body);
let operation = apply_batch_options(operation, &options);

let driver_response = self
let driver_result = self
.context
.driver
.execute_singleton_operation(operation, options.operation)
.await?;
.await;

Ok(BatchResponse::new(
crate::driver_bridge::driver_response_to_cosmos_response(driver_response),
self.context
.complete_result(driver_result, || self.operation_context("execute_batch"))?,
))
}

Expand Down
Loading
Loading