Skip to content

Commit

Permalink
Adding Diagnostic for change feed (#27031)
Browse files Browse the repository at this point in the history
### Packages impacted by this PR
@azure/cosmos

### Issues associated with this PR


### Describe the problem that is addressed by this PR


### What are the possible designs available to address the problem? If
there are more than one possible design, why was the one in this PR
chosen?


### Are there test cases added in this PR? _(If not, why?)_


### Provide a list of related PRs _(if any)_


### Command used to generate this PR:**_(Applicable only to SDK release
request PRs)_

### Checklists
- [ ] Added impacted package name to the issue description
- [ ] Does this PR needs any fixes in the SDK Generator?** _(If so,
create an Issue in the
[Autorest/typescript](https://github.com/Azure/autorest.typescript)
repository and link it here)_
- [ ] Added a changelog (if necessary)
  • Loading branch information
v1k1 authored Sep 7, 2023
1 parent 70304f0 commit b99018b
Show file tree
Hide file tree
Showing 24 changed files with 361 additions and 181 deletions.
3 changes: 2 additions & 1 deletion sdk/cosmosdb/cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Release History

## 3.17.4 (Unreleased)
## 4.0.0 (Unreleased)

### Features Added
- Added support for hierarchical partitions.
- Added Diagnostics to all response objects, i.e. ResourceResponse (parent class for ItemRespone, ContainerResponse etc.), FeedResponse, ChangeFeedIteratorResponse, ErrorResponse, BulkOperationResponse.

## 3.17.3 (2023-02-13)

Expand Down
64 changes: 63 additions & 1 deletion sdk/cosmosdb/cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ await container.item("id", undefined).read(); // None type
await container.item("id", null).read(); // null type
```

If the Partition Key consists of a single value, it could be supplied either as a lietral value, or an array.
If the Partition Key consists of a single value, it could be supplied either as a literal value, or an array.

```js
await container.item("id", "1").read();
Expand Down Expand Up @@ -326,6 +326,68 @@ setLogLevel("info");

For more detailed instructions on how to enable logs, you can look at the [@azure/logger package docs](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger).

### Diagnostics

Cosmos Diagnostics feature provides enhanced insights into all your client operations. A CosmosDiagnostics object is added to response of all client operations. such as
- Point look up operation reponse - `item.read()`, `container.create()`, `database.delete()`
- Query operation reponse -`queryIterator.fetchAll()`,
- Bulk and Batch operations -`item.batch()`.
- Error/Exception response objects.

A CosmosDiagnostics object is added to response of all client operations.
There are 3 Cosmos Diagnostic levels, info, debug and debug-unsafe. Where only info is meant for production systems and debug and debug-unsafe are meant to be used during development and debugging, since they consume significantly higher resources. Cosmos Diagnostic level can be set in 2 ways
- Programatically
```js
const client = new CosmosClient({ endpoint, key, diagnosticLevel: CosmosDbDiagnosticLevel.debug });
```
- Using environment variables
```bash
export AZURE_COSMOSDB_DIAGNOSTICS_LEVEL="debug"
```

Cosmos Diagnostic has three members
- ClientSideRequestStatistics Type: Contains aggregates diagnostic details, including metadata lookups, retries, endpoints contacted, and request and response statistics like payload size and duration. (is always collected, can be used in production systems.)

- DiagnosticNode: Is a tree-like structure that captures detailed diagnostic information. Similar to `har` recording present in browsers. This feature is disabled by default and is intended for debugging non-production environments only. (collected at diagnostic level debug and debug-unsafe)

- ClientConfig: Captures essential information related to client's configuration settings during client initialization. (collected at diagnostic level debug and debug-unsafe)

Please make sure to never set diagnostic level to `debug-unsafe` in production environment, since it this level `CosmosDiagnostics` captures request and response payloads and if you choose to log it (it is by default logged by @azure/logger at `verbose` level). These payloads might get captured in your log sinks.

#### Consuming Diagnostics

- Since `diagnostics` is added to all Response objects. You could programatically access `CosmosDiagnostic` as follows.
```js
// For point look up operations
const { container, diagnostics: containerCreateDiagnostic } =
await database.containers.createIfNotExists({
id: containerId,
partitionKey: {
paths: ["/key1"],
},
});

// For Batch operations
const operations: OperationInput[] = [
{
operationType: BulkOperationType.Create,
resourceBody: { id: 'A', key: "A", school: "high" },
},
];
const response = await container.items.batch(operations, "A");

// For query operations
const queryIterator = container.items.query("select * from c");
const { resources, diagnostics } = await queryIterator.fetchAll();

// While error handling
try {
// Some operation that might fail
} catch (err) {
const diagnostics = err.diagnostics
}
```
- You could also log `diagnostics` using `@azure/logger`, diagnostic is always logged using `@azure/logger` at `verbose` level. So if you set Diagnostic level to `debug` or `debug-unsafe` and `@azure/logger` level to `verbose`, `diagnostics` will be logged.
## Next steps

### More sample code
Expand Down
2 changes: 1 addition & 1 deletion sdk/cosmosdb/cosmos/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure/cosmos",
"version": "3.17.4",
"version": "4.0.0",
"description": "Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API",
"sdk-type": "client",
"keywords": [
Expand Down
92 changes: 77 additions & 15 deletions sdk/cosmosdb/cosmos/review/cosmos.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import { AbortError } from '@azure/abort-controller';
import { AbortSignal as AbortSignal_2 } from 'node-abort-controller';
import { Pipeline } from '@azure/core-rest-pipeline';
import { PipelineResponse } from '@azure/core-rest-pipeline';
import { RestError } from '@azure/core-rest-pipeline';
import { TokenCredential } from '@azure/core-auth';

Expand Down Expand Up @@ -79,6 +78,7 @@ export class ChangeFeedIteratorResponse<T> {
get activityId(): string;
get continuationToken(): string;
readonly count: number;
readonly diagnostics: CosmosDiagnostics;
headers: CosmosHeaders;
get requestCharge(): number;
readonly result: T;
Expand Down Expand Up @@ -836,6 +836,40 @@ export interface DeleteOperationInput {
partitionKey?: PartitionKey;
}

// @public (undocumented)
export type DiagnosticDataValue = {
selectedLocation: string;
activityId: string;
requestAttempNumber: number;
requestPayloadLengthInBytes: number;
responsePayloadLengthInBytes: number;
responseStatus: number;
readFromCache: boolean;
operationType: OperationType;
metadatOperationType: MetadataLookUpType;
resourceType: ResourceType;
failedAttempty: boolean;
successfulRetryPolicy: string;
partitionKeyRangeId: string;
stateful: boolean;
queryRecordsRead: number;
queryMethodIdentifier: string;
log: string[];
failure: boolean;
startTimeUTCInMs: number;
durationInMs: number;
requestData: Partial<{
requestPayloadLengthInBytes: number;
responsePayloadLengthInBytes: number;
operationType: OperationType;
resourceType: ResourceType;
headers: CosmosHeaders_2;
requestBody: any;
responseBody: any;
url: string;
}>;
};

// @public
export interface DiagnosticNode {
// (undocumented)
Expand All @@ -854,6 +888,46 @@ export interface DiagnosticNode {
startTimeUTCInMs: number;
}

// @public
export class DiagnosticNodeInternal implements DiagnosticNode {
// (undocumented)
children: DiagnosticNodeInternal[];
// (undocumented)
data: Partial<DiagnosticDataValue>;
// (undocumented)
durationInMs: number;
// (undocumented)
id: string;
// (undocumented)
nodeType: DiagnosticNodeType;
// (undocumented)
parent: DiagnosticNodeInternal;
// (undocumented)
startTimeUTCInMs: number;
}

// @public (undocumented)
export enum DiagnosticNodeType {
// (undocumented)
BACKGROUND_REFRESH_THREAD = "BACKGROUND_REFRESH_THREAD",
// (undocumented)
BATCH_REQUEST = "BATCH_REQUEST",
// (undocumented)
CLIENT_REQUEST_NODE = "CLIENT_REQUEST_NODE",
// (undocumented)
DEFAULT_QUERY_NODE = "DEFAULT_QUERY_NODE",
// (undocumented)
HTTP_REQUEST = "HTTP_REQUEST",
// (undocumented)
METADATA_REQUEST_NODE = "METADATA_REQUEST_NODE",
// (undocumented)
PARALLEL_QUERY_NODE = "PARALLEL_QUERY_NODE",
// (undocumented)
QUERY_REPAIR_NODE = "QUERY_REPAIR_NODE",
// (undocumented)
REQUEST_ATTEMPTS = "REQUEST_ATTEMPTS"
}

// @public (undocumented)
export interface ErrorBody {
// (undocumented)
Expand Down Expand Up @@ -893,11 +967,6 @@ export type ExistingKeyOperation = {
path: string;
};

// Warning: (ae-forgotten-export) The symbol "PartitionKeyInternal" needs to be exported by the entry point index.d.ts
//
// @public
export function extractPartitionKey(document: unknown, partitionKeyDefinition?: PartitionKeyDefinition): PartitionKeyInternal | undefined;

// @public
export interface FailedRequestAttemptDiagnostic {
// (undocumented)
Expand Down Expand Up @@ -994,7 +1063,6 @@ export enum GeospatialType {

// @public
export class GlobalEndpointManager {
constructor(options: CosmosClientOptions, readDatabaseAccount: (diagnosticNode: DiagnosticNodeInternal, opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>);
// (undocumented)
canUseMultipleWriteLocations(resourceType?: ResourceType, operationType?: OperationType): boolean;
enableEndpointDiscovery: boolean;
Expand Down Expand Up @@ -1194,13 +1262,11 @@ export enum MetadataLookUpType {
// (undocumented)
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
// (undocumented)
PartitionKeyDefinition = "PARTITION_KEY_DEFINITION",
DatabaseLookUp = "DATABASE_LOOK_UP",
// (undocumented)
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
// (undocumented)
QueryPlanLookUp = "QUERY_PLAN_LOOK_UP",
// (undocumented)
ServiceEndpointResolution = "SERVICE_ENDPOINT_RESOLUTION"
QueryPlanLookUp = "QUERY_PLAN_LOOK_UP"
}

// @public
Expand Down Expand Up @@ -2415,10 +2481,6 @@ export class Users {
upsert(body: UserDefinition, options?: RequestOptions): Promise<UserResponse>;
}

// Warnings were encountered during analysis:
//
// src/ClientContext.ts:110:5 - (ae-forgotten-export) The symbol "DiagnosticNodeInternal" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

```
12 changes: 6 additions & 6 deletions sdk/cosmosdb/cosmos/src/CosmosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,16 @@ export class CosmosClient {
): ClientConfigDiagnostic {
return {
endpoint: optionsOrConnectionString.endpoint,
resourceTokensConfigured: optionsOrConnectionString.resourceTokens === undefined,
tokenProviderConfigured: optionsOrConnectionString.tokenProvider === undefined,
aadCredentialsConfigured: optionsOrConnectionString.aadCredentials === undefined,
connectionPolicyConfigured: optionsOrConnectionString.connectionPolicy === undefined,
resourceTokensConfigured: optionsOrConnectionString.resourceTokens !== undefined,
tokenProviderConfigured: optionsOrConnectionString.tokenProvider !== undefined,
aadCredentialsConfigured: optionsOrConnectionString.aadCredentials !== undefined,
connectionPolicyConfigured: optionsOrConnectionString.connectionPolicy !== undefined,
consistencyLevel: optionsOrConnectionString.consistencyLevel,
defaultHeaders: optionsOrConnectionString.defaultHeaders,
agentConfigured: optionsOrConnectionString.agent === undefined,
agentConfigured: optionsOrConnectionString.agent !== undefined,
userAgentSuffix: optionsOrConnectionString.userAgentSuffix,
diagnosticLevel: optionsOrConnectionString.diagnosticLevel,
pluginsConfigured: optionsOrConnectionString.plugins === undefined,
pluginsConfigured: optionsOrConnectionString.plugins !== undefined,
sDKVersion: Constants.SDKVersion,
};
}
Expand Down
22 changes: 11 additions & 11 deletions sdk/cosmosdb/cosmos/src/CosmosDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ import { DiagnosticNodeInternal } from "./diagnostics/DiagnosticNodeInternal";
import { ConsistencyLevel } from "./documents";

/**
* This is a Cosmos Diagnostic type that holds diagnostic collected information during client operations. ie. Item.read(), Containes.create().
* * This is a Cosmos Diagnostic type that holds collected diagnostic information during a client operations. ie. Item.read(), Container.create().
* It has three members -
* 1. `clientSideRequestStatistics` member contains aggregate diagnostic information, including -
* - metadata lookups. Here all the server requests, apart from the final intended resource are considered as metadata calls.
* i.e. for item.read(id), if the client makes server call to discover endpoints it would be considered as metadata call.
* - retries
* - endpoints contacted.
* - request, response payload stats.
* - gatewayStatistics. information corresponding to main operation. For example during Item.read(), the client might perform many operations
* - gatewayStatistics - Information corresponding to main operation. For example during Item.read(), the client might perform many operations
* i.e. metadata lookup etc, but gatewayStatistics represents the diagnostics information for actual read operation.
*
* 2. diagnosticNode: Is a tree like structure which captures detailed diagnostic information. By default it is disabled, and is inteded to be
* 2. diagnosticNode - Is a tree like structure which captures detailed diagnostic information. By default it is disabled, and is intended to be
* used only for debugging on non production environments. The kind of details captured in diagnosticNode is controlled by `CosmosDbDiagnosticLevel`.
* - CosmosDbDiagnosticLevel.silent - is default value. In this level only clientSideRequestStatistics are captured. Is meant for production environments.
* - CosmosDbDiagnosticLevel.debug - captures diagnosticNode and clientConfig. No request and response payloads are captured. Is not meant to be used
* - CosmosDbDiagnosticLevel.info - Is default value. In this level only clientSideRequestStatistics are captured. Is is meant for production environments.
* - CosmosDbDiagnosticLevel.debug - Captures diagnosticNode and clientConfig. No request and response payloads are captured. Is not meant to be used
* in production environment.
* - CosmosDbDiagnosticLevel.debug-unsafe - in addition to data captured in CosmosDbDiagnosticLevel.debug, also captures request and response payloads.
* - CosmosDbDiagnosticLevel.debug-unsafe - In addition to data captured in CosmosDbDiagnosticLevel.debug, also captures request and response payloads.
* Is not meant to be used in production environment.
* 3. clientConfig: captures information related to Client's config.
* 3. clientConfig - Captures information related to how client was configured during initialization.
*/
export class CosmosDiagnostics {
public readonly clientSideRequestStatistics: ClientSideRequestStatistics;
Expand All @@ -45,7 +46,7 @@ export class CosmosDiagnostics {
}

/**
* This type holds information related to initilization of `CosmosClient`
* This type holds information related to initialization of `CosmosClient`
*/
export type ClientConfigDiagnostic = {
/**
Expand Down Expand Up @@ -163,10 +164,9 @@ export interface FailedRequestAttemptDiagnostic {
*/
export enum MetadataLookUpType {
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
ServiceEndpointResolution = "SERVICE_ENDPOINT_RESOLUTION",
PartitionKeyDefinition = "PARTITION_KEY_DEFINITION",
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
QueryPlanLookUp = "QUERY_PLAN_LOOK_UP",
DatabaseLookUp = "DATABASE_LOOK_UP",
ContainerLookUp = "CONTAINER_LOOK_UP",
}

Expand All @@ -179,7 +179,7 @@ export type ClientSideRequestStatistics = {
*/
requestStartTimeUTCInMs: number;
/**
* This is the duration in Milli seconds taken by client operation.
* This is the duration in milli seconds taken by client operation.
*/
requestDurationInMs: number;
/**
Expand Down
14 changes: 11 additions & 3 deletions sdk/cosmosdb/cosmos/src/client/ChangeFeed/ChangeFeedForEpkRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ChangeFeedPullModelIterator } from "./ChangeFeedPullModelIterator";
import { extractOverlappingRanges } from "./changeFeedUtils";
import { InternalChangeFeedIteratorOptions } from "./InternalChangeFeedOptions";
import { DiagnosticNodeInternal } from "../../diagnostics/DiagnosticNodeInternal";
import { withDiagnostics } from "../../utils/diagnostics";
import { getEmptyCosmosDiagnostics, withDiagnostics } from "../../utils/diagnostics";
/**
* @hidden
* Provides iterator for change feed for entire container or an epk range.
Expand Down Expand Up @@ -425,11 +425,19 @@ export class ChangeFeedForEpkRange<T> implements ChangeFeedPullModelIterator<T>
response.result,
response.result ? response.result.length : 0,
response.code,
response.headers
response.headers,
getEmptyCosmosDiagnostics()
);
} catch (err) {
// If any errors are encountered, eg. partition split or gone, handle it based on error code and not break the flow.
return new ChangeFeedIteratorResponse([], 0, err.code, err.headers, err.substatus);
return new ChangeFeedIteratorResponse(
[],
0,
err.code,
err.headers,
getEmptyCosmosDiagnostics(),
err.substatus
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ContinuationTokenForPartitionKey } from "./ContinuationTokenForPartitio
import { ChangeFeedPullModelIterator } from "./ChangeFeedPullModelIterator";
import { PartitionKey } from "../../documents";
import { DiagnosticNodeInternal } from "../../diagnostics/DiagnosticNodeInternal";
import { withDiagnostics } from "../../utils/diagnostics";
import { getEmptyCosmosDiagnostics, withDiagnostics } from "../../utils/diagnostics";
/**
* @hidden
* Provides iterator for change feed for one partition key.
Expand Down Expand Up @@ -162,7 +162,8 @@ export class ChangeFeedForPartitionKey<T> implements ChangeFeedPullModelIterator
response.result,
response.result ? response.result.length : 0,
response.code,
response.headers
response.headers,
getEmptyCosmosDiagnostics()
);
}
}
Loading

0 comments on commit b99018b

Please sign in to comment.