Loki: Add UI support for detected_level (#93574)

feat(detected_level): Add UI support for `detected_level`
This commit is contained in:
Sven Grossmann 2024-09-23 11:20:16 +02:00 committed by GitHub
parent da60c561a8
commit 7189a4af81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 100 additions and 14 deletions

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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]))'
);
});
});

View File

@ -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:

View File

@ -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' }];

View File

@ -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;