Skip to content

Commit 6a3e5dc

Browse files
authored
[Cosmos] Add logging changes and sample (#22288)
* adds changes to client pipeline and readme sample * fixed padding and improved ReadMe sample * moved headers to cosmos_headers, lowercased values * changes for fully working diagnostics * Update README.md * use latest azcore version * Update README.md * Update cosmos_client_test.go
1 parent d4af5af commit 6a3e5dc

10 files changed

Lines changed: 173 additions & 58 deletions

sdk/data/azcosmos/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,33 @@ You can create an Azure Cosmos account using:
3434

3535
In order to interact with the Azure Cosmos DB service you'll need to create an instance of the Cosmos client class. To make this possible you will need an URL and key of the Azure Cosmos DB service.
3636

37+
#### Logging
38+
39+
The SDK can make use of `azcore`'s logging implementation to collect useful information for debugging your application. In order to make use of logs, one must set the environment variable `"AZURE_SDK_GO_LOGGING"` to `"all"` like outlined in this [public document](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore#hdr-Built_in_Logging).
40+
41+
Once that is done, the SDK will begin to collect diagnostics. By default, it will output the logs to `stdout` - printing directly to your console - and will record all types of events (requests, responses, retries). If you'd like to configure a listener that acts differently, the small snippet below shows how you could do so.
42+
43+
```go
44+
import (
45+
"os"
46+
azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
47+
)
48+
49+
f, err := os.Create("cosmos-log-file.txt")
50+
handle(err)
51+
defer f.Close()
52+
53+
// Configure the listener to write to a file rather than to the console
54+
azlog.SetListener(func(event azlog.Event, s string) {
55+
f.WriteString(s + "\n")
56+
})
57+
58+
// Filter the types of events you'd like to log by removing the ones you're not interested in (if any)
59+
// We recommend using the default logging with no filters - but if filtering we recommend *always* including
60+
// `azlog.EventResponseError` since this is the event type that will help with debugging errors
61+
azlog.SetEvents(azlog.EventRequest, azlog.EventResponse, azlog.EventRetryPolicy, azlog.EventResponseError)
62+
```
63+
3764
## Examples
3865

3966
The following section provides several code snippets covering some of the most common Cosmos DB SQL API tasks, including:

sdk/data/azcosmos/cosmos_client.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ func newPipeline(authPolicy policy.Policy, gem *globalEndpointManager, options *
113113
if options == nil {
114114
options = &ClientOptions{}
115115
}
116-
117116
return azruntime.NewPipeline("azcosmos", serviceLibVersion,
118117
azruntime.PipelineOptions{
118+
AllowedHeaders: getAllowedHeaders(),
119119
PerCall: []policy.Policy{
120120
&headerPolicies{
121121
enableContentResponseOnWrite: options.EnableContentResponseOnWrite,
@@ -135,6 +135,7 @@ func newInternalPipeline(authPolicy policy.Policy, options *ClientOptions) azrun
135135
}
136136
return azruntime.NewPipeline("azcosmos", serviceLibVersion,
137137
azruntime.PipelineOptions{
138+
AllowedHeaders: getAllowedHeaders(),
138139
PerRetry: []policy.Policy{
139140
authPolicy,
140141
},
@@ -471,7 +472,7 @@ func (c *Client) executeAndEnsureSuccessResponse(request *policy.Request) (*http
471472
return response, nil
472473
}
473474

474-
return nil, newCosmosError(response)
475+
return nil, azruntime.NewResponseErrorWithErrorCode(response, response.Status)
475476
}
476477

477478
type pipelineRequestOptions struct {
@@ -481,3 +482,79 @@ type pipelineRequestOptions struct {
481482
isRidBased bool
482483
isWriteOperation bool
483484
}
485+
486+
func getAllowedHeaders() []string {
487+
return []string{
488+
cosmosHeaderRequestCharge,
489+
cosmosHeaderActivityId,
490+
cosmosHeaderEtag,
491+
cosmosHeaderSubstatus,
492+
cosmosHeaderPopulateQuotaInfo,
493+
cosmosHeaderPreTriggerInclude,
494+
cosmosHeaderPostTriggerInclude,
495+
cosmosHeaderIndexingDirective,
496+
cosmosHeaderSessionToken,
497+
cosmosHeaderConsistencyLevel,
498+
cosmosHeaderPrefer,
499+
cosmosHeaderIsUpsert,
500+
cosmosHeaderOfferThroughput,
501+
cosmosHeaderOfferAutoscale,
502+
cosmosHeaderQuery,
503+
cosmosHeaderOfferReplacePending,
504+
cosmosHeaderOfferMinimumThroughput,
505+
cosmosHeaderResponseContinuationTokenLimitInKb,
506+
cosmosHeaderEnableScanInQuery,
507+
cosmosHeaderMaxItemCount,
508+
cosmosHeaderContinuationToken,
509+
cosmosHeaderPopulateIndexMetrics,
510+
cosmosHeaderPopulateQueryMetrics,
511+
cosmosHeaderQueryMetrics,
512+
cosmosHeaderIndexUtilization,
513+
cosmosHeaderCorrelatedActivityId,
514+
cosmosHeaderIsBatchRequest,
515+
cosmosHeaderIsBatchAtomic,
516+
cosmosHeaderIsBatchOrdered,
517+
cosmosHeaderSDKSupportedCapabilities,
518+
headerXmsDate,
519+
headerContentType,
520+
headerIfMatch,
521+
headerIfNoneMatch,
522+
headerXmsVersion,
523+
headerContentLocation,
524+
headerXmsGatewayVersion,
525+
headerLsn,
526+
headerXmsCosmosLlsn,
527+
headerXmsCosmosItemLlsn,
528+
headerXmsItemLsn,
529+
headerXmsCosmosQuorumAckedLlsn,
530+
headerXmsCurrentReplicaSetSize,
531+
headerXmsCurrentWriteQuorum,
532+
headerXmsGlobalCommittedLsn,
533+
headerXmsLastStateChangeUtc,
534+
headerXmsNumberOfReadRegions,
535+
headerXmsQuorumAckedLsn,
536+
headerXmsRequestDurationMs,
537+
headerXmsResourceQuota,
538+
headerXmsResourceUsage,
539+
headerXmsSchemaVersion,
540+
headerXmsServiceVersion,
541+
headerXmsTransportRequestId,
542+
headerXmsXpRole,
543+
headerCollectionPartitionIndex,
544+
headerCollectionServiceIndex,
545+
headerXmsDocumentDbPartitionKeyRangeId,
546+
cosmosHeaderPhysicalPartitionId,
547+
headerStrictTransportSecurity,
548+
headerXmsDatabaseAccountConsumedMb,
549+
headerXmsDatabaseAccountProvisionedMb,
550+
headerXmsDatabaseAccountReservedMb,
551+
headerXmsMaxMediaStorageUsageMb,
552+
headerXmsMediaStorageUsageMb,
553+
headerXmsContentPath,
554+
headerXmsAltContentPath,
555+
cosmosHeaderMaxContentLength,
556+
cosmosHeaderIsPartitionKeyDeletePending,
557+
cosmosHeaderQueryExecutionInfo,
558+
headerXmsItemCount,
559+
}
560+
}

sdk/data/azcosmos/cosmos_client_test.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"net/url"
1212
"strconv"
13+
"strings"
1314
"testing"
1415

1516
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -75,9 +76,7 @@ func TestNewClientFromConnStrSuccess(t *testing.T) {
7576
}
7677

7778
func TestEnsureErrorIsGeneratedOnResponse(t *testing.T) {
78-
someError := &cosmosErrorResponse{
79-
Code: "SomeCode",
80-
}
79+
someError := map[string]string{"Code": "SomeCode"}
8180

8281
jsonString, err := json.Marshal(someError)
8382
if err != nil {
@@ -102,13 +101,24 @@ func TestEnsureErrorIsGeneratedOnResponse(t *testing.T) {
102101
}
103102

104103
asError := err.(*azcore.ResponseError)
105-
if asError.ErrorCode != someError.Code {
106-
t.Errorf("Expected %v, but got %v", someError.Code, asError.ErrorCode)
104+
if asError.ErrorCode != "404 Not Found" {
105+
t.Errorf("Expected %v, but got %v", "404 Not Found", asError.ErrorCode)
106+
}
107+
108+
// Verify error body
109+
responseBody, err2 := io.ReadAll(asError.RawResponse.Body)
110+
if err2 != nil {
111+
t.Errorf("Error reading response body: %v\n", err)
112+
}
113+
stringBody := string(responseBody)
114+
if !strings.Contains(stringBody, "SomeCode") {
115+
t.Errorf("Expected %v to contain %v", stringBody, "SomeCode")
107116
}
108117

109118
if err.Error() != asError.Error() {
110119
t.Errorf("Expected %v, but got %v", err.Error(), asError.Error())
111120
}
121+
asError.RawResponse.Body.Close()
112122
}
113123

114124
func TestEnsureErrorIsNotGeneratedOnResponse(t *testing.T) {

sdk/data/azcosmos/cosmos_error.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

sdk/data/azcosmos/cosmos_error_test.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1616
azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
1717
"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
18+
"github.com/stretchr/testify/assert"
1819
)
1920

2021
func TestCosmosErrorOnEmptyResponse(t *testing.T) {
@@ -32,13 +33,13 @@ func TestCosmosErrorOnEmptyResponse(t *testing.T) {
3233
resp, _ := pl.Do(req)
3334

3435
var azErr *azcore.ResponseError
35-
if err := newCosmosError(resp); !errors.As(err, &azErr) {
36+
if err := azruntime.NewResponseErrorWithErrorCode(resp, resp.Status); !errors.As(err, &azErr) {
3637
t.Fatalf("unexpected error type %T", err)
3738
}
3839
if azErr.StatusCode != http.StatusNotFound {
3940
t.Errorf("unexpected status code %d", azErr.StatusCode)
4041
}
41-
if azErr.ErrorCode != "" {
42+
if azErr.ErrorCode != "404 Not Found" {
4243
t.Errorf("unexpected error code %s", azErr.ErrorCode)
4344
}
4445
if azErr.RawResponse == nil {
@@ -62,13 +63,13 @@ func TestCosmosErrorOnNonJsonBody(t *testing.T) {
6263
resp, _ := pl.Do(req)
6364

6465
var azErr *azcore.ResponseError
65-
if err := newCosmosError(resp); !errors.As(err, &azErr) {
66+
if err := azruntime.NewResponseErrorWithErrorCode(resp, resp.Status); !errors.As(err, &azErr) {
6667
t.Fatalf("unexpected error type %T", err)
6768
}
6869
if azErr.StatusCode != http.StatusNotFound {
6970
t.Errorf("unexpected status code %d", azErr.StatusCode)
7071
}
71-
if azErr.ErrorCode != "" {
72+
if azErr.ErrorCode != "404 Not Found" {
7273
t.Errorf("unexpected error code %s", azErr.ErrorCode)
7374
}
7475
if azErr.RawResponse == nil {
@@ -80,9 +81,7 @@ func TestCosmosErrorOnNonJsonBody(t *testing.T) {
8081
}
8182

8283
func TestCosmosErrorOnJsonBody(t *testing.T) {
83-
someError := &cosmosErrorResponse{
84-
Code: "SomeCode",
85-
}
84+
someError := map[string]string{"Code": "SomeCode"}
8685

8786
jsonString, err := json.Marshal(someError)
8887
if err != nil {
@@ -104,13 +103,15 @@ func TestCosmosErrorOnJsonBody(t *testing.T) {
104103
resp, _ := pl.Do(req)
105104

106105
var azErr *azcore.ResponseError
107-
if err := newCosmosError(resp); !errors.As(err, &azErr) {
106+
err2 := azruntime.NewResponseErrorWithErrorCode(resp, resp.Status)
107+
assert.Error(t, err2)
108+
if err := azruntime.NewResponseErrorWithErrorCode(resp, resp.Status); !errors.As(err, &azErr) {
108109
t.Fatalf("unexpected error type %T", err)
109110
}
110111
if azErr.StatusCode != http.StatusNotFound {
111112
t.Errorf("unexpected status code %d", azErr.StatusCode)
112113
}
113-
if azErr.ErrorCode != someError.Code {
114+
if azErr.ErrorCode != "404 Not Found" {
114115
t.Errorf("unexpected error code %s", azErr.ErrorCode)
115116
}
116117
if azErr.RawResponse == nil {

sdk/data/azcosmos/cosmos_global_endpoint_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func (gem *globalEndpointManager) GetAccountProperties(ctx context.Context) (acc
139139
return properties, nil
140140
}
141141

142-
return accountProperties{}, newCosmosError(azResponse)
142+
return accountProperties{}, azruntime.NewResponseErrorWithErrorCode(azResponse, azResponse.Status)
143143
}
144144

145145
func newAccountProperties(azResponse *http.Response) (accountProperties, error) {

sdk/data/azcosmos/cosmos_headers.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const (
77
cosmosHeaderRequestCharge string = "x-ms-request-charge"
88
cosmosHeaderActivityId string = "x-ms-activity-id"
99
cosmosHeaderEtag string = "etag"
10+
cosmosHeaderSubstatus string = "x-ms-substatus"
1011
cosmosHeaderPopulateQuotaInfo string = "x-ms-documentdb-populatequotainfo"
1112
cosmosHeaderPreTriggerInclude string = "x-ms-documentdb-pre-trigger-include"
1213
cosmosHeaderPostTriggerInclude string = "x-ms-documentdb-post-trigger-include"
@@ -40,6 +41,42 @@ const (
4041
headerIfMatch string = "If-Match"
4142
headerIfNoneMatch string = "If-None-Match"
4243
headerXmsVersion string = "x-ms-version"
44+
headerContentLocation string = "content-location"
45+
headerXmsGatewayVersion string = "x-ms-gatewayversion"
46+
headerLsn string = "lsn"
47+
headerXmsCosmosLlsn string = "x-ms-cosmos-llsn"
48+
headerXmsCosmosItemLlsn string = "x-ms-cosmos-item-llsn"
49+
headerXmsItemLsn string = "x-ms-item-lsn"
50+
headerXmsCosmosQuorumAckedLlsn string = "x-ms-cosmos-quorum-acked-llsn"
51+
headerXmsCurrentReplicaSetSize string = "x-ms-current-replica-set-size"
52+
headerXmsCurrentWriteQuorum string = "x-ms-current-write-quorum"
53+
headerXmsGlobalCommittedLsn string = "x-ms-global-committed-lsn"
54+
headerXmsLastStateChangeUtc string = "x-ms-last-state-change-utc"
55+
headerXmsNumberOfReadRegions string = "x-ms-number-of-read-regions"
56+
headerXmsQuorumAckedLsn string = "x-ms-quorum-acked-lsn"
57+
headerXmsRequestDurationMs string = "x-ms-request-duration-ms"
58+
headerXmsResourceQuota string = "x-ms-resource-quota"
59+
headerXmsResourceUsage string = "x-ms-resource-usage"
60+
headerXmsSchemaVersion string = "x-ms-schemaversion"
61+
headerXmsServiceVersion string = "x-ms-serviceversion"
62+
headerXmsTransportRequestId string = "x-ms-transport-request-id"
63+
headerXmsXpRole string = "x-ms-xp-role"
64+
headerCollectionPartitionIndex string = "collection-partition-index"
65+
headerCollectionServiceIndex string = "collection-service-index"
66+
headerXmsDocumentDbPartitionKeyRangeId string = "x-ms-documentdb-partitionkeyrangeid"
67+
cosmosHeaderPhysicalPartitionId string = "x-ms-cosmos-physical-partition-id"
68+
headerStrictTransportSecurity string = "strict-transport-security"
69+
headerXmsDatabaseAccountConsumedMb string = "x-ms-databaseaccount-consumed-mb"
70+
headerXmsDatabaseAccountProvisionedMb string = "x-ms-databaseaccount-provisioned-mb"
71+
headerXmsDatabaseAccountReservedMb string = "x-ms-databaseaccount-reserved-mb"
72+
headerXmsMaxMediaStorageUsageMb string = "x-ms-max-media-storage-usage-mb"
73+
headerXmsMediaStorageUsageMb string = "x-ms-media-storage-usage-mb"
74+
headerXmsContentPath string = "x-ms-content-path"
75+
headerXmsAltContentPath string = "x-ms-alt-content-path"
76+
cosmosHeaderMaxContentLength string = "x-ms-cosmos-max-content-length"
77+
cosmosHeaderIsPartitionKeyDeletePending string = "x-ms-cosmos-is-partition-key-delete-pending"
78+
cosmosHeaderQueryExecutionInfo string = "x-ms-cosmos-query-execution-info"
79+
headerXmsItemCount string = "x-ms-item-count"
4380
)
4481

4582
const (

sdk/data/azcosmos/cosmos_offers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (c cosmosOffers) ReadThroughputIfExists(
5656
if len(theOffers.Offers) == 0 {
5757
azResponse.StatusCode = http.StatusNotFound
5858
azResponse.Header.Add(cosmosHeaderRequestCharge, fmt.Sprint(queryRequestCharge))
59-
return ThroughputResponse{}, newCosmosError(azResponse)
59+
return ThroughputResponse{}, azruntime.NewResponseErrorWithErrorCode(azResponse, azResponse.Status)
6060
}
6161

6262
// Now read the individual offer

sdk/data/azcosmos/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.18
44

55
require (
66
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
7-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2
7+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0
88
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0
99
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2
1010
github.com/stretchr/testify v1.8.4

sdk/data/azcosmos/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
22
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
3-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ=
4-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM=
3+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4=
4+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM=
55
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0 h1:Yoicul8bnVdQrhDMTHxdEckRGX01XvwXDHUT9zYZ3k0=
66
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
77
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=

0 commit comments

Comments
 (0)