From 70f600db1036a1032702a41d7349ea193c765f37 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Tue, 7 Mar 2023 14:35:19 -0800 Subject: [PATCH] Cloudwatch Logs: Make mixed type fields fallback to being strings (#63981) * Cloudwatch Logs: make mixed type fields fallback to being strings * addressing pr comments --- pkg/tsdb/cloudwatch/log_query.go | 20 +++++++++- pkg/tsdb/cloudwatch/log_query_test.go | 56 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/log_query.go b/pkg/tsdb/cloudwatch/log_query.go index 6de78a1c246..7bd4a5390b4 100644 --- a/pkg/tsdb/cloudwatch/log_query.go +++ b/pkg/tsdb/cloudwatch/log_query.go @@ -73,9 +73,14 @@ func logsResultsToDataframes(response *cloudwatchlogs.GetQueryResultsOutput) (*d timeField[i] = &parsedTime } else if numericField, ok := fieldValues[*resultField.Field].([]*float64); ok { parsedFloat, err := strconv.ParseFloat(*resultField.Value, 64) + if err != nil { - return nil, err + // This can happen if a field has a mix of numeric and non-numeric values. + // In that case, we change the field from a numeric field to a string field. + fieldValues[*resultField.Field] = changeToStringField(rowCount, nonEmptyRows[:i+1], *resultField.Field) + continue } + numericField[i] = &parsedFloat } else { fieldValues[*resultField.Field].([]*string)[i] = resultField.Value @@ -145,6 +150,19 @@ func logsResultsToDataframes(response *cloudwatchlogs.GetQueryResultsOutput) (*d return frame, nil } +func changeToStringField(lengthOfValues int, rows [][]*cloudwatchlogs.ResultField, logEventField string) []*string { + fieldValuesAsStrings := make([]*string, lengthOfValues) + for i, resultFields := range rows { + for _, field := range resultFields { + if *field.Field == logEventField { + fieldValuesAsStrings[i] = field.Value + } + } + } + + return fieldValuesAsStrings +} + func groupResults(results *data.Frame, groupingFieldNames []string) ([]*data.Frame, error) { groupingFields := make([]*data.Field, 0) diff --git a/pkg/tsdb/cloudwatch/log_query_test.go b/pkg/tsdb/cloudwatch/log_query_test.go index ab387289e80..ed9be9b6d7c 100644 --- a/pkg/tsdb/cloudwatch/log_query_test.go +++ b/pkg/tsdb/cloudwatch/log_query_test.go @@ -221,6 +221,62 @@ func TestLogsResultsToDataframes(t *testing.T) { assert.ElementsMatch(t, expectedDataframe.Fields, dataframes.Fields) } +func TestLogsResultsToDataframes_MixedTypes_NumericValuesMixedWithStringFallBackToStringValues(t *testing.T) { + dataframes, err := logsResultsToDataframes(&cloudwatchlogs.GetQueryResultsOutput{ + Results: [][]*cloudwatchlogs.ResultField{ + { + &cloudwatchlogs.ResultField{ + Field: aws.String("numberOrString"), + Value: aws.String("-1.234"), + }, + }, + { + &cloudwatchlogs.ResultField{ + Field: aws.String("numberOrString"), + Value: aws.String("1"), + }, + }, + { + &cloudwatchlogs.ResultField{ + Field: aws.String("numberOrString"), + Value: aws.String("not a number"), + }, + }, + { + &cloudwatchlogs.ResultField{ + Field: aws.String("numberOrString"), + Value: aws.String("2.000"), + }, + }, + }, + Status: aws.String("ok"), + }) + require.NoError(t, err) + + expectedDataframe := &data.Frame{ + Name: "CloudWatchLogsResponse", + Fields: []*data.Field{ + data.NewField("numberOrString", nil, []*string{ + aws.String("-1.234"), + aws.String("1"), + aws.String("not a number"), + aws.String("2.000"), + }), + }, + RefID: "", + Meta: &data.FrameMeta{ + Custom: map[string]interface{}{ + "Status": "ok", + }, + }, + } + + assert.Equal(t, expectedDataframe.Name, dataframes.Name) + assert.Equal(t, expectedDataframe.RefID, dataframes.RefID) + assert.Equal(t, expectedDataframe.Meta, dataframes.Meta) + assert.ElementsMatch(t, expectedDataframe.Fields, dataframes.Fields) +} + func TestGroupKeyGeneration(t *testing.T) { logField := data.NewField("@log", data.Labels{}, []*string{ aws.String("fakelog-a"),