diff --git a/public/app/features/logs/components/LogLabels.tsx b/public/app/features/logs/components/LogLabels.tsx index 6ad502f7830..9fa3e77b112 100644 --- a/public/app/features/logs/components/LogLabels.tsx +++ b/public/app/features/logs/components/LogLabels.tsx @@ -5,7 +5,7 @@ import { GrafanaTheme2, Labels } from '@grafana/data'; import { Tooltip, useStyles2 } from '@grafana/ui'; // Levels are already encoded in color, filename is a Loki-ism -const HIDDEN_LABELS = ['level', 'lvl', 'filename']; +const HIDDEN_LABELS = ['detected_level', 'level', 'lvl', 'filename']; interface Props { labels: Labels; diff --git a/public/app/features/logs/legacyLogsFrame.ts b/public/app/features/logs/legacyLogsFrame.ts index 9b9ac9bc011..e6b3bbc4399 100644 --- a/public/app/features/logs/legacyLogsFrame.ts +++ b/public/app/features/logs/legacyLogsFrame.ts @@ -45,7 +45,7 @@ export function parseLegacyLogsFrame(frame: DataFrame): LogsFrame | null { } const timeNanosecondField = cache.getFieldByName('tsNs') ?? null; - const severityField = cache.getFieldByName('level') ?? null; + const severityField = cache.getFieldByName('detected_level') ?? cache.getFieldByName('level') ?? null; const idField = cache.getFieldByName('id') ?? null; // extracting the labels is done very differently for old-loki-style and simple-style diff --git a/public/app/features/logs/logsModel.test.ts b/public/app/features/logs/logsModel.test.ts index f6922147ddd..be57e697532 100644 --- a/public/app/features/logs/logsModel.test.ts +++ b/public/app/features/logs/logsModel.test.ts @@ -360,6 +360,87 @@ describe('dataFrameToLogsModel', () => { }); }); + it('given one series should return expected logs model with detected_level', () => { + const series: DataFrame[] = [ + createDataFrame({ + fields: [ + { + name: 'time', + type: FieldType.time, + values: ['2019-04-26T09:28:11.352440161Z', '2019-04-26T14:42:50.991981292Z'], + }, + { + name: 'message', + type: FieldType.string, + values: ['foo=bar', 'foo=bar'], + labels: { + job: 'grafana', + }, + }, + { + name: 'detected_level', + type: FieldType.string, + values: ['info', 'error'], + }, + ], + meta: { + limit: 1000, + }, + refId: 'A', + }), + ]; + const logsModel = dataFrameToLogsModel(series, 1); + expect(logsModel.hasUniqueLabels).toBeFalsy(); + expect(logsModel.rows).toHaveLength(2); + expect(logsModel.rows).toMatchObject([ + { + entry: 'foo=bar', + labels: { job: 'grafana' }, + logLevel: 'info', + uniqueLabels: {}, + uid: 'A_0', + }, + { + entry: 'foo=bar', + labels: { job: 'grafana' }, + logLevel: 'error', + uniqueLabels: {}, + uid: 'A_1', + }, + ]); + + expect(logsModel.series).toHaveLength(2); + expect(logsModel.series).toMatchObject([ + { + name: 'info', + fields: [ + { type: 'time', values: [1556270891000, 1556289770000] }, + { type: 'number', values: [1, 0] }, + ], + }, + { + name: 'error', + fields: [ + { type: 'time', values: [1556289770000] }, + { type: 'number', values: [1] }, + ], + }, + ]); + expect(logsModel.meta).toHaveLength(2); + expect(logsModel.meta![0]).toMatchObject({ + label: COMMON_LABELS, + value: { + job: 'grafana', + }, + kind: LogsMetaKind.LabelsMap, + }); + expect(logsModel.meta![1]).toMatchObject({ + label: LIMIT_LABEL, + value: `1000 (2 returned)`, + kind: LogsMetaKind.String, + }); + }); + it('with infinite scrolling enabled it should return expected logs model', () => { config.featureToggles.logsInfiniteScrolling = true; diff --git a/public/app/features/logs/logsModel.ts b/public/app/features/logs/logsModel.ts index 1bbe72776b6..76c68a6132c 100644 --- a/public/app/features/logs/logsModel.ts +++ b/public/app/features/logs/logsModel.ts @@ -424,7 +424,7 @@ export function logSeriesToLogsModel( } let logLevel = LogLevel.unknown; - const logLevelKey = (logLevelField && logLevelField.values[j]) || (labels && labels['level']); + const logLevelKey = (logLevelField && logLevelField.values[j]) || (labels?.detected_level ?? labels?.level); if (typeof logLevelKey === 'number' || typeof logLevelKey === 'string') { logLevel = getLogLevelFromKey(logLevelKey); } else { @@ -633,7 +633,7 @@ function defaultExtractLevel(dataFrame: DataFrame): LogLevel { } function getLogLevelFromLabels(labels: Labels): LogLevel { - const level = labels['level'] ?? labels['lvl'] ?? labels['loglevel'] ?? ''; + const level = labels['detected_level'] ?? labels['level'] ?? labels['lvl'] ?? labels['loglevel'] ?? ''; return level ? getLogLevelFromKey(level) : LogLevel.unknown; } diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 926436df970..4f9f6ca3372 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1429,11 +1429,10 @@ describe('LokiDatasource', () => { } ) ).toEqual({ - expr: 'sum by (level) (count_over_time({label="value"} | drop __error__[$__auto]))', + expr: 'sum by (level, detected_level) (count_over_time({label="value"} | drop __error__[$__auto]))', queryType: LokiQueryType.Range, refId: 'log-volume-A', supportingQueryType: SupportingQueryType.LogsVolume, - legendFormat: '{{ level }}', }); }); @@ -1448,11 +1447,10 @@ describe('LokiDatasource', () => { } ) ).toEqual({ - expr: 'sum by (level) (count_over_time({label="value"} | drop __error__[$__auto]))', + expr: 'sum by (level, detected_level) (count_over_time({label="value"} | drop __error__[$__auto]))', queryType: LokiQueryType.Range, refId: 'log-volume-A', supportingQueryType: SupportingQueryType.LogsVolume, - legendFormat: '{{ level }}', }); }); @@ -1468,8 +1466,7 @@ describe('LokiDatasource', () => { } ) ).toEqual({ - expr: 'sum by (level) (count_over_time({label="value"} | drop __error__[$__auto]))', - legendFormat: '{{ level }}', + expr: 'sum by (level, detected_level) (count_over_time({label="value"} | drop __error__[$__auto]))', queryType: 'range', refId: 'log-volume-A', supportingQueryType: 'logsVolume', @@ -1510,7 +1507,9 @@ describe('LokiDatasource', () => { refId: 'A', } ); - expect(query?.expr).toEqual('sum by (level) (count_over_time({label="value"} | drop __error__[$__auto]))'); + expect(query?.expr).toEqual( + 'sum by (level, detected_level) (count_over_time({label="value"} | drop __error__[$__auto]))' + ); }); }); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index ed4c01f5d5b..859b718c38c 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -217,7 +217,7 @@ export class LokiDatasource } const dropErrorExpression = `${expr} | drop __error__`; - const field = options.field || 'level'; + const field = options.field || 'level, detected_level'; if (isQueryWithError(this.interpolateString(dropErrorExpression, placeHolderScopedVars)) === false) { expr = dropErrorExpression; } @@ -228,7 +228,6 @@ export class LokiDatasource queryType: LokiQueryType.Range, supportingQueryType: SupportingQueryType.LogsVolume, expr: `sum by (${field}) (count_over_time(${expr}[$__auto]))`, - legendFormat: `{{ ${field} }}`, }; case SupplementaryQueryType.LogsSample: diff --git a/public/app/plugins/datasource/loki/responseUtils.test.ts b/public/app/plugins/datasource/loki/responseUtils.test.ts index f961007968a..b5746e78fbe 100644 --- a/public/app/plugins/datasource/loki/responseUtils.test.ts +++ b/public/app/plugins/datasource/loki/responseUtils.test.ts @@ -130,6 +130,11 @@ describe('extractLevelLikeLabelFromDataFrame', () => { input.fields[1].values = [{ error_level: 'info' }]; expect(extractLevelLikeLabelFromDataFrame(input)).toBe('error_level'); }); + it('returns label if detected_level label is present', () => { + const input = cloneDeep(frame); + input.fields[1].values = [{ detected_level: 'info' }]; + expect(extractLevelLikeLabelFromDataFrame(input)).toBe('detected_level'); + }); it('returns undefined if no level-like label is present', () => { const input = cloneDeep(frame); input.fields[1].values = [{ foo: 'info' }]; diff --git a/public/app/plugins/datasource/loki/responseUtils.ts b/public/app/plugins/datasource/loki/responseUtils.ts index fd18c8f6c88..1b1e909fefb 100644 --- a/public/app/plugins/datasource/loki/responseUtils.ts +++ b/public/app/plugins/datasource/loki/responseUtils.ts @@ -121,7 +121,9 @@ export function extractLevelLikeLabelFromDataFrame(frame: DataFrame): string | n // Find first level-like label for (let labels of labelsArray) { - const label = Object.keys(labels).find((label) => label === 'lvl' || label.includes('level')); + const label = Object.keys(labels).find( + (label) => label === 'detected_level' || label === 'level' || label === 'lvl' || label.includes('level') + ); if (label) { levelLikeLabel = label; break;