AzureMonitor: Parse non-fatal errors for Logs (#51320)

This commit is contained in:
Andres Martinez Gotor 2022-06-24 08:56:58 +02:00 committed by GitHub
parent 9e80e44b45
commit b10ddfdf8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 174 additions and 6 deletions

View File

@ -148,7 +148,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
// If azureLogAnalyticsSameAs is defined and set to false, return an error
if sameAs, ok := dsInfo.JSONData["azureLogAnalyticsSameAs"]; ok && !sameAs.(bool) {
return dataResponseErrorWithExecuted(fmt.Errorf("Log Analytics credentials are no longer supported. Go to the data source configuration to update Azure Monitor credentials")) //nolint:golint,stylecheck
return dataResponseErrorWithExecuted(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"))
}
req, err := e.createRequest(ctx, dsInfo, url)
@ -187,7 +187,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
return dataResponseErrorWithExecuted(err)
}
frame, err := ResponseTableToFrame(t)
frame, err := ResponseTableToFrame(t, logResponse)
if err != nil {
return dataResponseErrorWithExecuted(err)
}
@ -234,9 +234,31 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, dsInfo
return req, nil
}
// Error definition has been inferred from real data and other model definitions like
// https://github.com/Azure/azure-sdk-for-go/blob/3640559afddbad452d265b54fb1c20b30be0b062/services/preview/virtualmachineimagebuilder/mgmt/2019-05-01-preview/virtualmachineimagebuilder/models.go
type AzureLogAnalyticsAPIError struct {
Details *[]AzureLogAnalyticsAPIErrorBase `json:"details,omitempty"`
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
}
type AzureLogAnalyticsAPIErrorBase struct {
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
Innererror *AzureLogAnalyticsInnerError `json:"innererror,omitempty"`
}
type AzureLogAnalyticsInnerError struct {
Code *string `json:"code,omitempty"`
Message *string `json:"message,omitempty"`
Severity *int `json:"severity,omitempty"`
SeverityName *string `json:"severityName,omitempty"`
}
// AzureLogAnalyticsResponse is the json response object from the Azure Log Analytics API.
type AzureLogAnalyticsResponse struct {
Tables []types.AzureResponseTable `json:"tables"`
Error *AzureLogAnalyticsAPIError `json:"error,omitempty"`
}
// GetPrimaryResultTable returns the first table in the response named "PrimaryResult", or an

View File

@ -235,7 +235,7 @@ func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) {
if res.Error == nil {
t.Fatal("expecting an error")
}
if !strings.Contains(res.Error.Error(), "Log Analytics credentials are no longer supported") {
if !strings.Contains(res.Error.Error(), "credentials for Log Analytics are no longer supported") {
t.Error("expecting the error to inform of bad credentials")
}
}

View File

@ -5,14 +5,45 @@ import (
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
)
func apiErrorToNotice(err *AzureLogAnalyticsAPIError) data.Notice {
message := []string{}
severity := data.NoticeSeverityWarning
if err.Message != nil {
message = append(message, *err.Message)
}
if err.Details != nil && len(*err.Details) > 0 {
for _, detail := range *err.Details {
if detail.Message != nil {
message = append(message, *detail.Message)
}
if detail.Innererror != nil {
if detail.Innererror.Message != nil {
message = append(message, *detail.Innererror.Message)
}
if detail.Innererror.SeverityName != nil && *detail.Innererror.SeverityName == "Error" {
// Severity names are not documented in the API response format
// https://docs.microsoft.com/en-us/azure/azure-monitor/logs/api/response-format
// so assuming either an error or a warning
severity = data.NoticeSeverityError
}
}
}
}
return data.Notice{
Severity: severity,
Text: strings.Join(message, " "),
}
}
// ResponseTableToFrame converts an AzureResponseTable to a data.Frame.
func ResponseTableToFrame(table *types.AzureResponseTable) (*data.Frame, error) {
func ResponseTableToFrame(table *types.AzureResponseTable, res AzureLogAnalyticsResponse) (*data.Frame, error) {
converterFrame, err := converterFrameForTable(table)
if err != nil {
return nil, err
@ -25,6 +56,11 @@ func ResponseTableToFrame(table *types.AzureResponseTable) (*data.Frame, error)
}
}
}
if res.Error != nil {
converterFrame.Frame.AppendNotices(apiErrorToNotice(res.Error))
}
return converterFrame.Frame, nil
}

View File

@ -139,12 +139,42 @@ func TestLogTableToFrame(t *testing.T) {
return frame
},
},
{
name: "data and error in real response",
testFile: "loganalytics/9-log-analytics-response-error.json",
expectedFrame: func() *data.Frame {
frame := data.NewFrame("",
data.NewField("OperationName", nil, []*string{pointer.String("Create or Update Virtual Machine")}),
data.NewField("Level", nil, []*string{pointer.String("Informational")}),
)
frame.Meta = &data.FrameMeta{
Custom: &LogAnalyticsMeta{ColumnTypes: []string{"string", "string"}},
Notices: []data.Notice{{Severity: data.NoticeSeverityError, Text: "There were some errors when processing your query. Something went wrong processing your query on the server. The results of this query exceed the set limit of 1 records."}},
}
return frame
},
},
{
name: "data and warning in real response",
testFile: "loganalytics/10-log-analytics-response-warning.json",
expectedFrame: func() *data.Frame {
frame := data.NewFrame("",
data.NewField("OperationName", nil, []*string{pointer.String("Create or Update Virtual Machine")}),
data.NewField("Level", nil, []*string{pointer.String("Informational")}),
)
frame.Meta = &data.FrameMeta{
Custom: &LogAnalyticsMeta{ColumnTypes: []string{"string", "string"}},
Notices: []data.Notice{{Severity: data.NoticeSeverityWarning, Text: "There were some errors when processing your query. Something went wrong processing your query on the server. Not sure what happened."}},
}
return frame
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := loadLogAnalyticsTestFileWithNumber(t, tt.testFile)
frame, err := ResponseTableToFrame(&res.Tables[0])
frame, err := ResponseTableToFrame(&res.Tables[0], res)
require.NoError(t, err)
if diff := cmp.Diff(tt.expectedFrame(), frame, data.FrameTestCompareOptions()...); diff != "" {

View File

@ -188,7 +188,7 @@ func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, query *
return dataResponseErrorWithExecuted(err)
}
frame, err := loganalytics.ResponseTableToFrame(&argResponse.Data)
frame, err := loganalytics.ResponseTableToFrame(&argResponse.Data, loganalytics.AzureLogAnalyticsResponse{})
if err != nil {
return dataResponseErrorWithExecuted(err)
}

View File

@ -0,0 +1,40 @@
{
"tables": [
{
"name": "PrimaryResult",
"columns": [
{
"name": "OperationName",
"type": "string"
},
{
"name": "Level",
"type": "string"
}
],
"rows": [
[
"Create or Update Virtual Machine",
"Informational"
]
]
}
],
"error": {
"code": "PartialError",
"message": "There were some errors when processing your query.",
"details": [
{
"code": "EngineError",
"message": "Something went wrong processing your query on the server.",
"innererror": {
"code": "-2133196797",
"message": "Not sure what happened.",
"severity": 2,
"severityName": "Warning"
}
}
]
}
}

View File

@ -0,0 +1,40 @@
{
"tables": [
{
"name": "PrimaryResult",
"columns": [
{
"name": "OperationName",
"type": "string"
},
{
"name": "Level",
"type": "string"
}
],
"rows": [
[
"Create or Update Virtual Machine",
"Informational"
]
]
}
],
"error": {
"code": "PartialError",
"message": "There were some errors when processing your query.",
"details": [
{
"code": "EngineError",
"message": "Something went wrong processing your query on the server.",
"innererror": {
"code": "-2133196797",
"message": "The results of this query exceed the set limit of 1 records.",
"severity": 2,
"severityName": "Error"
}
}
]
}
}