Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
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 @@ -9,6 +9,8 @@
- Added `FeedOptions::max_fan_out` (and `FeedOptions::with_max_fan_out`) to cap how many physical partitions a cross-partition query or change feed may fan out to. Applies to `ContainerClient::query_items` and `ContainerClient::query_change_feed`. The cap is enforced only at initial query setup; a partition that splits mid-execution and pushes the fan-out higher does not abort the operation. ([#4855](https://github.com/Azure/azure-sdk-for-rust/pull/4855))
- 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 `metrics`-gated `CosmosMetricsHandler` (with `MetricsOptions`), emitting the stable `db.client.operation.duration` histogram plus per-signal opt-in metrics (`with_request_charge_metric`, `with_returned_rows_metric`) and an opt-in extended attribute set (`with_extended_attributes`); a no-op when no meter provider is registered. ([#4789](https://github.com/Azure/azure-sdk-for-rust/pull/4789))
- Added `MetricsOptions::with_active_instance_metric`, an opt-in `azure.cosmosdb.client.active_instance.count` up-down counter reporting the number of live `CosmosClient` instances per account endpoint, keyed on `server.address` (plus `server.port` for a non-default port). ([#4874](https://github.com/Azure/azure-sdk-for-rust/pull/4874))
- Added `DiagnosticsHandler::on_client_created`, a defaulted hook that lets a handler observe client construction (`CosmosClientInfo`) and return a `ClientLifetimeToken` dropped with the client, for handlers that need to track client lifetime. ([#4874](https://github.com/Azure/azure-sdk-for-rust/pull/4874))
- Added composable tail-sampled emission handlers — a `TracingLogHandler` leaf that writes a compact `tracing` line and a `SamplingLogHandler` wrapper (holding an `Arc<dyn DiagnosticsHandler>`) that applies the sampling gate plus a shared per-window rate limit, defaulting to wrap a `TracingLogHandler` — and the `distributed_tracing`-gated `CosmosTracingHandler` (backdated span tree), also rate-limited so an error storm can't overwhelm exporters. All emit only for operations which fail or breach a configurable `DiagnosticsThresholds`, and stamp *why* they were sampled (a failure, or which threshold) on the emitted line and span. ([#4789](https://github.com/Azure/azure-sdk-for-rust/pull/4789))
- Added the non-default `control_plane` feature that gates the control-plane APIs (database and container CRUD, and throughput/offer management). It is intentionally independent of `key_auth` so these APIs are not tied to key-based authentication. ([#4854](https://github.com/Azure/azure-sdk-for-rust/pull/4854))

Expand All @@ -19,6 +21,8 @@

### Bugs Fixed

- The Cosmos tracing span's operation label now prefers the caller-facing `CosmosOperationContext` identity over the driver-recorded name, matching how the `db.operation.name` metric attribute is resolved. Previously an aggregate whose surfaced sub-operation differed from the caller's operation — such as a PATCH that fails during its internal read — could label the span `read_item` while the metric reported `patch_item`. ([#4874](https://github.com/Azure/azure-sdk-for-rust/pull/4874))

### Other Changes

- Existing `ResponseHeaders` accessors now return Gateway 2.0 backend duration, quota, item-count, and local-LSN response metadata. ([#4797](https://github.com/Azure/azure-sdk-for-rust/pull/4797))
Expand Down
38 changes: 16 additions & 22 deletions sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl ContainerClient {
self.container_ref.account(),
self.container_ref.rid(),
options.operation,
self.operation_context("read_throughput"),
self.operation_context("read_container_throughput"),
)
.await
}
Expand Down Expand Up @@ -231,7 +231,7 @@ impl ContainerClient {
self.container_ref.rid(),
throughput,
options.operation,
self.operation_context("replace_throughput"),
self.operation_context("replace_container_throughput"),
)
.await
}
Expand Down Expand Up @@ -889,16 +889,13 @@ impl ContainerClient {
if let Some(hint) = options.feed.max_item_count {
initial_operation = initial_operation.with_max_item_count(hint);
}
let plan = self
.context
.driver
.plan_operation(
initial_operation,
&options.operation,
options.feed.continuation_token.as_ref(),
&options.feed.to_plan_options(),
)
.await?;
let plan = Box::pin(self.context.driver.plan_operation(
initial_operation,
&options.operation,
options.feed.continuation_token.as_ref(),
&options.feed.to_plan_options(),
))
.await?;
Ok(QueryItemIterator::new(
self.context.driver.clone(),
Some(self.container_ref.clone()),
Expand Down Expand Up @@ -1076,16 +1073,13 @@ impl ContainerClient {
// precedence. The driver owns the mapping to wire headers.
initial_operation = initial_operation.with_change_feed_start(start_from);

let plan = self
.context
.driver
.plan_operation(
initial_operation,
&options.operation,
options.feed.continuation_token.as_ref(),
&options.feed.to_plan_options(),
)
.await?;
let plan = Box::pin(self.context.driver.plan_operation(
initial_operation,
&options.operation,
options.feed.continuation_token.as_ref(),
&options.feed.to_plan_options(),
))
.await?;

Ok(ChangeFeedPageIterator::new(
self.context.driver.clone(),
Expand Down
17 changes: 7 additions & 10 deletions sdk/cosmos/azure_data_cosmos/src/clients/cosmos_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,13 @@ impl CosmosClient {
CosmosOperation::query_databases(account).with_body(serde_json::to_vec(&query)?);
let operation_options = options.operation;

let plan = self
.context
.driver
.plan_operation(
initial_operation,
&operation_options,
None,
&PlanOptions::default(),
)
.await?;
let plan = Box::pin(self.context.driver.plan_operation(
Comment thread
NaluTripician marked this conversation as resolved.
Outdated
initial_operation,
&operation_options,
None,
&PlanOptions::default(),
))
.await?;

Ok(QueryItemIterator::new(
self.context.driver.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::sync::Arc;

use crate::{
clients::{resolve_binary_encoding, ClientContext},
diagnostics::DiagnosticsHandler,
diagnostics::{CosmosClientInfo, DiagnosticsHandler},
options::{
BinaryEncodingOptions, CosmosClientOptions, OperationOptions, PartitionFailoverOptions,
ThroughputControlGroupOptions, UserAgentSuffix,
Expand Down Expand Up @@ -298,6 +298,10 @@ impl CosmosClientBuilder {
let (account_endpoint, credential) = account.into_parts();
let endpoint = account_endpoint.into_url();

// Capture the account coordinates for client-scoped diagnostics before
// the endpoint is moved into the driver account.
let client_info = CosmosClientInfo::from_endpoint(&endpoint);

// Clone credential for the driver before the SDK consumes it for auth policy.
let driver_credential = credential.clone();

Expand All @@ -321,11 +325,12 @@ impl CosmosClientBuilder {
let driver = runtime.into_inner().create_driver(driver_options).await?;

Ok(CosmosClient {
context: ClientContext {
context: ClientContext::new(
driver,
binary_encoding: resolve_binary_encoding(self.options.binary_encoding),
diagnostics_handlers: self.options.diagnostics_handlers,
},
resolve_binary_encoding(self.options.binary_encoding),
self.options.diagnostics_handlers,
&client_info,
),
})
}
}
Expand Down
21 changes: 9 additions & 12 deletions sdk/cosmos/azure_data_cosmos/src/clients/database_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,13 @@ impl DatabaseClient {
.with_body(serde_json::to_vec(&query)?);
let operation_options = options.operation;

let plan = self
.context
.driver
.plan_operation(
initial_operation,
&operation_options,
None,
&PlanOptions::default(),
)
.await?;
let plan = Box::pin(self.context.driver.plan_operation(
initial_operation,
&operation_options,
None,
&PlanOptions::default(),
))
.await?;

Ok(QueryItemIterator::new(
self.context.driver.clone(),
Expand Down Expand Up @@ -267,7 +264,7 @@ impl DatabaseClient {
self.context.driver.account(),
&resource_id,
options.operation,
self.operation_context("read_throughput"),
self.operation_context("read_database_throughput"),
)
.await
}
Expand Down Expand Up @@ -314,7 +311,7 @@ impl DatabaseClient {
&resource_id,
throughput,
options.operation,
self.operation_context("replace_throughput"),
self.operation_context("replace_database_throughput"),
)
.await
}
Expand Down
32 changes: 31 additions & 1 deletion sdk/cosmos/azure_data_cosmos/src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ use std::sync::Arc;

use azure_data_cosmos_driver::CosmosDriver;

use crate::diagnostics::{CosmosOperationContext, DiagnosticsContext, DiagnosticsHandlerChain};
use crate::diagnostics::{
ClientLifetimeToken, CosmosClientInfo, CosmosOperationContext, DiagnosticsContext,
DiagnosticsHandlerChain,
};
use crate::models::CosmosResponse;
use crate::options::BinaryEncodingOptions;

Expand All @@ -62,9 +65,36 @@ pub(crate) struct ClientContext {
/// Empty by default, in which case the completion path does nothing beyond
/// checking whether a handler is present.
pub(crate) diagnostics_handlers: DiagnosticsHandlerChain,
/// Lifetime tokens handed back by the handlers when this client was built.
///
/// Never read; held solely so the tokens' `Drop` runs when the last client
/// derived from this context goes away. Because the context is cloned down
/// into every `DatabaseClient`/`ContainerClient`, the shared `Arc` keeps the
/// tokens alive for as long as *any* of those clients is reachable, which is
/// the lifetime handlers are meant to observe.
_client_tokens: Arc<[ClientLifetimeToken]>,
}

impl ClientContext {
/// Builds the shared context for a newly constructed
/// [`CosmosClient`](super::CosmosClient), notifying every registered handler
/// that a client came online and taking ownership of the lifetime tokens
/// they hand back.
pub(crate) fn new(
driver: Arc<CosmosDriver>,
binary_encoding: BinaryEncodingOptions,
diagnostics_handlers: DiagnosticsHandlerChain,
client_info: &CosmosClientInfo,
) -> Self {
let client_tokens = diagnostics_handlers.dispatch_client_created(client_info);
Self {
driver,
binary_encoding,
diagnostics_handlers,
_client_tokens: client_tokens,
}
}

/// Converts a completed driver response into the SDK
/// [`CosmosResponse`](crate::models::CosmosResponse) and invokes the
/// diagnostics handler chain for the operation.
Expand Down
6 changes: 6 additions & 0 deletions sdk/cosmos/azure_data_cosmos/src/diagnostics/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub(crate) const DB_RESPONSE_STATUS_CODE: &str = "db.response.status_code";
/// `server.address` — the host contacted for the request.
pub(crate) const SERVER_ADDRESS: &str = "server.address";

/// `server.port` — the port contacted for the request.
///
/// Per semantic conventions this is emitted only when the port differs from the
/// scheme's default (443 for HTTPS).
pub(crate) const SERVER_PORT: &str = "server.port";

/// `error.type` — a low-cardinality identifier of the error (the status code).
pub(crate) const ERROR_TYPE: &str = "error.type";

Expand Down
Loading
Loading