From f8661654456d254f54661e6070c1da8a3fdb2ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Mon, 16 May 2022 08:29:46 +0200 Subject: [PATCH] loki: backend: add error-prop if loki parse error happened (#48860) --- .../loki/backendResultTransformer.test.ts | 37 ++++++++++++++--- .../loki/backendResultTransformer.ts | 15 +++++-- .../datasource/loki/responseUtils.test.ts | 41 +++++++++++++++++++ .../plugins/datasource/loki/responseUtils.ts | 6 +++ .../datasource/loki/result_transformer.ts | 3 +- 5 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 public/app/plugins/datasource/loki/responseUtils.test.ts create mode 100644 public/app/plugins/datasource/loki/responseUtils.ts diff --git a/public/app/plugins/datasource/loki/backendResultTransformer.test.ts b/public/app/plugins/datasource/loki/backendResultTransformer.test.ts index 098b7d276c3..5c8e9c69180 100644 --- a/public/app/plugins/datasource/loki/backendResultTransformer.test.ts +++ b/public/app/plugins/datasource/loki/backendResultTransformer.test.ts @@ -39,12 +39,11 @@ const inputFrame: DataFrame = { { name: 'labels', type: FieldType.string, - config: { - custom: { - json: true, - }, - }, - values: new ArrayVector(['{ "level": "info", "code": "41🌙" }', '{ "level": "error", "code": "41🌙" }']), + config: {}, + values: new ArrayVector([ + { level: 'info', code: '41🌙' }, + { level: 'error', code: '41🌙' }, + ]), }, { name: 'tsNs', @@ -157,4 +156,30 @@ describe('loki backendResultTransformer', () => { result.data[0].fields.filter((field: Field) => field.name === 'derived1' && field.type === 'string').length ).toBe(1); }); + + it('handle loki parsing errors', () => { + const clonedFrame = cloneDeep(inputFrame); + clonedFrame.fields[2] = { + name: 'labels', + type: FieldType.string, + config: {}, + values: new ArrayVector([ + { level: 'info', code: '41🌙', __error__: 'LogfmtParserErr' }, + { level: 'error', code: '41🌙' }, + ]), + }; + const response: DataQueryResponse = { data: [clonedFrame] }; + + const result = transformBackendResult( + response, + [ + { + refId: 'A', + expr: LOKI_EXPR, + }, + ], + [] + ); + expect(result.data[0]?.meta?.custom?.error).toBe('Error when parsing some of the logs'); + }); }); diff --git a/public/app/plugins/datasource/loki/backendResultTransformer.ts b/public/app/plugins/datasource/loki/backendResultTransformer.ts index 29c9b7a0f3b..5b559209f89 100644 --- a/public/app/plugins/datasource/loki/backendResultTransformer.ts +++ b/public/app/plugins/datasource/loki/backendResultTransformer.ts @@ -3,6 +3,7 @@ import { DataQueryResponse, DataFrame, isDataFrame, FieldType, QueryResultMeta } import { getDerivedFields } from './getDerivedFields'; import { makeTableFrames } from './makeTableFrames'; import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils'; +import { dataFrameHasLokiError } from './responseUtils'; import { DerivedFieldConfig, LokiQuery, LokiQueryType } from './types'; function isMetricFrame(frame: DataFrame): boolean { @@ -25,14 +26,20 @@ function processStreamFrame( query: LokiQuery | undefined, derivedFieldConfigs: DerivedFieldConfig[] ): DataFrame { + const custom: Record = { + // used by logs_model + lokiQueryStatKey: 'Summary: total bytes processed', + }; + + if (dataFrameHasLokiError(frame)) { + custom.error = 'Error when parsing some of the logs'; + } + const meta: QueryResultMeta = { preferredVisualisationType: 'logs', limit: query?.maxLines, searchWords: query !== undefined ? getHighlighterExpressionsFromQuery(formatQuery(query.expr)) : undefined, - custom: { - // used by logs_model - lokiQueryStatKey: 'Summary: total bytes processed', - }, + custom, }; const newFrame = setFrameMeta(frame, meta); diff --git a/public/app/plugins/datasource/loki/responseUtils.test.ts b/public/app/plugins/datasource/loki/responseUtils.test.ts new file mode 100644 index 00000000000..992c05ea221 --- /dev/null +++ b/public/app/plugins/datasource/loki/responseUtils.test.ts @@ -0,0 +1,41 @@ +import { cloneDeep } from 'lodash'; + +import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; + +import { dataFrameHasLokiError } from './responseUtils'; + +const frame: DataFrame = { + length: 1, + fields: [ + { + name: 'Time', + config: {}, + type: FieldType.time, + values: new ArrayVector([1]), + }, + { + name: 'labels', + config: {}, + type: FieldType.other, + values: new ArrayVector([{ level: 'info' }]), + }, + { + name: 'Line', + config: {}, + type: FieldType.string, + values: new ArrayVector(['line1']), + }, + ], +}; + +describe('dataframeHasParsingError', () => { + it('handles frame with parsing error', () => { + const input = cloneDeep(frame); + input.fields[1].values = new ArrayVector([{ level: 'info', __error__: 'error' }]); + expect(dataFrameHasLokiError(input)).toBe(true); + }); + it('handles frame without parsing error', () => { + const input = cloneDeep(frame); + expect(dataFrameHasLokiError(input)).toBe(false); + }); +}); diff --git a/public/app/plugins/datasource/loki/responseUtils.ts b/public/app/plugins/datasource/loki/responseUtils.ts new file mode 100644 index 00000000000..033d174d525 --- /dev/null +++ b/public/app/plugins/datasource/loki/responseUtils.ts @@ -0,0 +1,6 @@ +import { DataFrame, Labels } from '@grafana/data'; + +export function dataFrameHasLokiError(frame: DataFrame): boolean { + const labelSets: Labels[] = frame.fields.find((f) => f.name === 'labels')?.values.toArray() ?? []; + return labelSets.some((labels) => labels.__error__ !== undefined); +} diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index e32974fb18a..5e855e67ad7 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -25,6 +25,7 @@ import TableModel from 'app/core/table_model'; import { renderLegendFormat } from '../prometheus/legend'; import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils'; +import { dataFrameHasLokiError } from './responseUtils'; import { LokiRangeQueryRequest, LokiResponse, @@ -346,7 +347,7 @@ export function lokiStreamsToDataFrames( const dataFrame = lokiStreamsToRawDataFrame(data, target.refId); enhanceDataFrame(dataFrame, config); - if (meta.custom && dataFrame.fields.some((f) => f.labels && Object.keys(f.labels).some((l) => l === '__error__'))) { + if (meta.custom && dataFrameHasLokiError(dataFrame)) { meta.custom.error = 'Error when parsing some of the logs'; }