Skip to content

Commit f2ce733

Browse files
gracewilcoxgracewilcox
andauthored
Change ErrorInfo Format (Azure#19240)
* start * changed results error * update tests * new ErrorInfo format * tidy * fix readme * add to tests/readme * fix link Co-authored-by: gracewilcox <gracewilcox@DESKTOP-J3MEOK3.localdomain>
1 parent 51f8104 commit f2ce733

File tree

7 files changed

+133
-164
lines changed

7 files changed

+133
-164
lines changed

sdk/monitor/azquery/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features Added
66

77
### Breaking Changes
8+
* Changed format of `ErrorInfo` to custom error type
89

910
### Bugs Fixed
1011

sdk/monitor/azquery/README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
2222

2323
* An [Azure subscription][azure_sub]
2424
* A supported Go version (the Azure SDK supports the two most recent Go releases)
25-
* For log queries, a Log Analytics workspace.
26-
* For metric queries, a Resource URI.
25+
* For log queries, an [Azure Log Analytics workspace][log_analytics_workspace_create] ID.
26+
* For metric queries, the Resource URI of any Azure resource (Storage Account, Key Vault, CosmosDB, etc).
2727

2828
### Authentication
2929

@@ -77,7 +77,7 @@ For examples of Logs and Metrics queries, see the [Examples](#examples) section
7777

7878
The Log Analytics service applies throttling when the request rate is too high. Limits, such as the maximum number of rows returned, are also applied on the Kusto queries. For more information, see [Query API](https://docs.microsoft.com/azure/azure-monitor/service-limits#la-query-api).
7979

80-
If you're executing a batch logs query, a throttled request will return a `LogsQueryError` object. That object's `code` value will be `ThrottledError`.
80+
If you're executing a batch logs query, a throttled request will return a `ErrorInfo` object. That object's `code` value will be `ThrottledError`.
8181

8282
### Metrics data structure
8383

@@ -136,8 +136,8 @@ full example: [link][example_query_workspace]
136136
```
137137
Body
138138
|---Query *string // Kusto Query
139-
|---Timespan *string // ISO8601 Standard Timespan- refer to timespan section for more info
140-
|---Workspaces []*string //Optional- additional workspaces to query
139+
|---Timespan *string // ISO8601 Standard Timespan
140+
|---Workspaces []*string // Optional- additional workspaces to query
141141
```
142142

143143
#### Logs query result structure
@@ -150,6 +150,7 @@ Results
150150
|---Name *string
151151
|---Rows [][]interface{}
152152
|---Error *ErrorInfo
153+
|---Code *string // custom error type
153154
|---Render interface{}
154155
|---Statistics interface{}
155156
```
@@ -193,7 +194,8 @@ BatchRequest
193194
BatchResponse
194195
|---Responses []*BatchQueryResponse
195196
|---Body *BatchQueryResults
196-
|---Error *ErrorInfo
197+
|---Error *ErrorInfo // custom error type
198+
|---Code *string
197199
|---Render interface{}
198200
|---Statistics interface{}
199201
|---Tables []*Table
@@ -301,6 +303,12 @@ Response
301303

302304
## Troubleshooting
303305

306+
### Error Handling
307+
308+
All methods which send HTTP requests return `*azcore.ResponseError` when these requests fail. `ResponseError` has error details and the raw response from Monitor Query.
309+
310+
For Logs, an error may also be returned in the response's `ErrorInfo` struct, usually to indicate a partial error from the service.
311+
304312
### Logging
305313

306314
This module uses the logging implementation in `azcore`. To turn on logging for all Azure SDK modules, set `AZURE_SDK_GO_LOGGING` to `all`. By default the logger writes to stderr. Use the `azcore/log` package to control log output. For example, logging only HTTP request and response events, and printing them to stdout:
@@ -341,10 +349,12 @@ comments.
341349
[azure_monitor_create_using_portal]: https://docs.microsoft.com/azure/azure-monitor/logs/quick-create-workspace
342350
[azure_monitor_overview]: https://docs.microsoft.com/azure/azure-monitor/overview
343351
[context]: https://pkg.go.dev/context
352+
[default_cred_ref]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity#defaultazurecredential
344353
[example_batch]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery#example-LogsClient.Batch
345354
[example_query_workspace]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery#example-LogsClient.QueryWorkspace
346355
[kusto_query_language]: https://learn.microsoft.com/azure/data-explorer/kusto/query/
347356
[log_analytics_workspace]: https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-workspace-overview
357+
[log_analytics_workspace_create]: https://learn.microsoft.com/azure/azure-monitor/logs/quick-create-workspace?tabs=azure-portal
348358
[time_go]: https://pkg.go.dev/time
349359
[time_intervals]: https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
350360

sdk/monitor/azquery/autorest.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ directive:
9797
- from: models_serde.go
9898
where: $
9999
transform: return $.replace(/(?:\/\/.*\s)+func \(\w \*?(?:ErrorResponse|ErrorResponseAutoGenerated)\).*\{\s(?:.+\s)+\}\s/g, "");
100+
- from: models.go
101+
where: $
102+
transform: return $.replace(/(?:\/\/.*\s)+type (?:ErrorInfo|ErrorDetail).+\{(?:\s.+\s)+\}\s/g, "");
103+
- from: models_serde.go
104+
where: $
105+
transform: return $.replace(/(?:\/\/.*\s)+func \(\w \*?(?:ErrorInfo|ErrorDetail)\).*\{\s(?:.+\s)+\}\s/g, "");
100106

101107
# delete generated constructor
102108
- from: logs_client.go

sdk/monitor/azquery/custom_client.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ package azquery
99
// this file contains handwritten additions to the generated code
1010

1111
import (
12+
"encoding/json"
13+
"fmt"
14+
1215
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1316
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1417
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
@@ -45,3 +48,29 @@ func NewMetricsClient(credential azcore.TokenCredential, options *MetricsClientO
4548
}
4649

4750
const metricsHost string = "https://management.azure.com"
51+
52+
// ErrorInfo - The code and message for an error.
53+
type ErrorInfo struct {
54+
// REQUIRED; A machine readable error code.
55+
Code string
56+
57+
// full error message detailing why the operation failed.
58+
data []byte
59+
}
60+
61+
// UnmarshalJSON implements the json.Unmarshaller interface for type ErrorInfo.
62+
func (e *ErrorInfo) UnmarshalJSON(data []byte) error {
63+
e.data = data
64+
ei := struct{ Code string }{}
65+
if err := json.Unmarshal(data, &ei); err != nil {
66+
return fmt.Errorf("unmarshalling type %T: %v", e, err)
67+
}
68+
e.Code = ei.Code
69+
70+
return nil
71+
}
72+
73+
// Error implements a custom error for type ErrorInfo.
74+
func (e *ErrorInfo) Error() string {
75+
return string(e.data)
76+
}

sdk/monitor/azquery/logs_client_test.go

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ package azquery_test
88

99
import (
1010
"context"
11+
"errors"
12+
"strings"
1113
"testing"
1214

15+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1316
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
1417
"github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery"
1518
"github.com/stretchr/testify/require"
@@ -31,58 +34,72 @@ func TestQueryWorkspace_BasicQuerySuccess(t *testing.T) {
3134
t.Fatalf("error with query, %s", err.Error())
3235
}
3336

34-
if res.Results.Error != nil {
37+
if res.Error != nil {
3538
t.Fatal("expended Error to be nil")
3639
}
37-
if res.Results.Render != nil {
40+
if res.Render != nil {
3841
t.Fatal("expended Render to be nil")
3942
}
40-
if res.Results.Statistics != nil {
43+
if res.Statistics != nil {
4144
t.Fatal("expended Statistics to be nil")
4245
}
43-
if len(res.Results.Tables) != 1 {
46+
if len(res.Tables) != 1 {
4447
t.Fatal("expected one table")
4548
}
46-
if len(res.Results.Tables[0].Rows) != 100 {
49+
if len(res.Tables[0].Rows) != 100 {
4750
t.Fatal("expected 100 rows")
4851
}
4952

50-
testSerde(t, &res.Results)
53+
testSerde(t, &res)
5154
}
5255

5356
func TestQueryWorkspace_BasicQueryFailure(t *testing.T) {
5457
client := startLogsTest(t)
55-
query := "not a valid query"
56-
body := azquery.Body{
57-
Query: &query,
58-
}
5958

60-
res, err := client.QueryWorkspace(context.Background(), workspaceID, body, nil)
59+
res, err := client.QueryWorkspace(context.Background(), workspaceID, azquery.Body{Query: to.Ptr("not a valid query")}, nil)
6160
if err == nil {
62-
t.Fatalf("expected BadArgumentError")
61+
t.Fatalf("expected an error")
62+
}
63+
if res.Error != nil {
64+
t.Fatal("expected no error code")
6365
}
64-
if res.Results.Tables != nil {
66+
if res.Tables != nil {
6567
t.Fatalf("expected no results")
6668
}
67-
testSerde(t, &res.Results)
69+
70+
var httpErr *azcore.ResponseError
71+
if !errors.As(err, &httpErr) {
72+
t.Fatal("expected an azcore.ResponseError")
73+
}
74+
if httpErr.ErrorCode != "BadArgumentError" {
75+
t.Fatal("expected a BadArgumentError")
76+
}
77+
if httpErr.StatusCode != 400 {
78+
t.Fatal("expected a 400 error")
79+
}
80+
81+
testSerde(t, &res)
6882
}
6983

7084
func TestQueryWorkspace_PartialError(t *testing.T) {
7185
client := startLogsTest(t)
7286
query := "let Weight = 92233720368547758; range x from 1 to 3 step 1 | summarize percentilesw(x, Weight * 100, 50)"
73-
body := azquery.Body{
74-
Query: &query,
75-
}
7687

77-
res, err := client.QueryWorkspace(context.Background(), workspaceID, body, nil)
88+
res, err := client.QueryWorkspace(context.Background(), workspaceID, azquery.Body{Query: &query}, nil)
7889
if err != nil {
7990
t.Fatal("error with query")
8091
}
81-
if *res.Results.Error.Code != "PartialError" {
92+
if res.Error == nil {
93+
t.Fatal("expected an error")
94+
}
95+
if res.Error.Code != "PartialError" {
8296
t.Fatal("expected a partial error")
8397
}
98+
if !strings.Contains(res.Error.Error(), "PartialError") {
99+
t.Fatal("expected error message to contain PartialError")
100+
}
84101

85-
testSerde(t, &res.Results)
102+
testSerde(t, &res)
86103
}
87104

88105
// tests for special options: timeout, statistics, visualization
@@ -99,16 +116,16 @@ func TestQueryWorkspace_AdvancedQuerySuccess(t *testing.T) {
99116
if err != nil {
100117
t.Fatalf("error with query, %s", err.Error())
101118
}
102-
if res.Results.Tables == nil {
119+
if res.Tables == nil {
103120
t.Fatal("expected Tables results")
104121
}
105-
if res.Results.Error != nil {
122+
if res.Error != nil {
106123
t.Fatal("expended Error to be nil")
107124
}
108-
if res.Results.Render == nil {
125+
if res.Render == nil {
109126
t.Fatal("expended Render results")
110127
}
111-
if res.Results.Statistics == nil {
128+
if res.Statistics == nil {
112129
t.Fatal("expended Statistics results")
113130
}
114131
}
@@ -126,10 +143,10 @@ func TestQueryWorkspace_MultipleWorkspaces(t *testing.T) {
126143
if err != nil {
127144
t.Fatalf("error with query, %s", err.Error())
128145
}
129-
if res.Results.Error != nil {
146+
if res.Error != nil {
130147
t.Fatal("result error should be nil")
131148
}
132-
if len(res.Results.Tables[0].Rows) != 100 {
149+
if len(res.Tables[0].Rows) != 100 {
133150
t.Fatalf("expected 100 results, received")
134151
}
135152
}
@@ -148,10 +165,24 @@ func TestBatch_QuerySuccess(t *testing.T) {
148165
if err != nil {
149166
t.Fatalf("expected non nil error: %s", err.Error())
150167
}
151-
if len(res.BatchResponse.Responses) != 2 {
168+
if len(res.Responses) != 2 {
152169
t.Fatal("expected two responses")
153170
}
154-
testSerde(t, &res.BatchResponse)
171+
for _, resp := range res.Responses {
172+
if resp.Body.Error != nil {
173+
t.Fatal("expected a successful response")
174+
}
175+
if resp.Body.Tables == nil {
176+
t.Fatal("expected a response")
177+
}
178+
if *resp.ID == "1" && len(resp.Body.Tables[0].Rows) != 100 {
179+
t.Fatal("expected 100 rows from batch request 1")
180+
}
181+
if *resp.ID == "2" && len(resp.Body.Tables[0].Rows) != 2 {
182+
t.Fatal("expected 100 rows from batch request 1")
183+
}
184+
}
185+
testSerde(t, &res)
155186
}
156187

157188
func TestBatch_PartialError(t *testing.T) {
@@ -166,9 +197,30 @@ func TestBatch_PartialError(t *testing.T) {
166197
if err != nil {
167198
t.Fatalf("expected non nil error: %s", err.Error())
168199
}
169-
if len(res.BatchResponse.Responses) != 2 {
200+
if len(res.Responses) != 2 {
170201
t.Fatal("expected two responses")
171202
}
203+
for _, resp := range res.Responses {
204+
if *resp.ID == "1" {
205+
if resp.Body.Error == nil {
206+
t.Fatal("expected batch request 1 to fail")
207+
}
208+
if resp.Body.Error.Code != "BadArgumentError" {
209+
t.Fatal("expected BadArgumentError")
210+
}
211+
if !strings.Contains(resp.Body.Error.Error(), "BadArgumentError") {
212+
t.Fatal("expected error message to contain BadArgumentError")
213+
}
214+
}
215+
if *resp.ID == "2" {
216+
if resp.Body.Error != nil {
217+
t.Fatal("expected batch request 2 to succeed")
218+
}
219+
if len(resp.Body.Tables[0].Rows) != 100 {
220+
t.Fatal("expected 100 rows")
221+
}
222+
}
223+
}
172224
}
173225

174226
func TestLogConstants(t *testing.T) {

sdk/monitor/azquery/models.go

Lines changed: 0 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)