mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Refactor: Move LogLevel and Labels utils to @grafana/ui (#16285)
* rename Tags to Labels in SeriesData * move some logs stuff to grafana/ui * add roundtrip tests
This commit is contained in:
committed by
Torkel Ödegaard
parent
d0d5b38572
commit
bfba47c6c4
@@ -7,4 +7,5 @@ export * from './theme';
|
||||
export * from './graph';
|
||||
export * from './threshold';
|
||||
export * from './input';
|
||||
export * from './logs';
|
||||
export * from './displayValue';
|
||||
|
||||
21
packages/grafana-ui/src/types/logs.ts
Normal file
21
packages/grafana-ui/src/types/logs.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Mapping of log level abbreviation to canonical log level.
|
||||
* Supported levels are reduce to limit color variation.
|
||||
*/
|
||||
export enum LogLevel {
|
||||
emerg = 'critical',
|
||||
alert = 'critical',
|
||||
crit = 'critical',
|
||||
critical = 'critical',
|
||||
warn = 'warning',
|
||||
warning = 'warning',
|
||||
err = 'error',
|
||||
eror = 'error',
|
||||
error = 'error',
|
||||
info = 'info',
|
||||
notice = 'info',
|
||||
dbug = 'debug',
|
||||
debug = 'debug',
|
||||
trace = 'trace',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
@@ -8,5 +8,7 @@ export * from './csv';
|
||||
export * from './statsCalculator';
|
||||
export * from './displayValue';
|
||||
export * from './deprecationWarning';
|
||||
export * from './logs';
|
||||
export * from './labels';
|
||||
export { getMappedValue } from './valueMappings';
|
||||
export * from './validate';
|
||||
|
||||
55
packages/grafana-ui/src/utils/labels.test.ts
Normal file
55
packages/grafana-ui/src/utils/labels.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { parseLabels, formatLabels, findCommonLabels, findUniqueLabels } from './labels';
|
||||
|
||||
describe('parseLabels()', () => {
|
||||
it('returns no labels on empty labels string', () => {
|
||||
expect(parseLabels('')).toEqual({});
|
||||
expect(parseLabels('{}')).toEqual({});
|
||||
});
|
||||
|
||||
it('returns labels on labels string', () => {
|
||||
expect(parseLabels('{foo="bar", baz="42"}')).toEqual({ foo: 'bar', baz: '42' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatLabels()', () => {
|
||||
it('returns no labels on empty label set', () => {
|
||||
expect(formatLabels({})).toEqual('');
|
||||
expect(formatLabels({}, 'foo')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('returns label string on label set', () => {
|
||||
expect(formatLabels({ foo: 'bar', baz: '42' })).toEqual('{baz="42", foo="bar"}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCommonLabels()', () => {
|
||||
it('returns no common labels on empty sets', () => {
|
||||
expect(findCommonLabels([{}])).toEqual({});
|
||||
expect(findCommonLabels([{}, {}])).toEqual({});
|
||||
});
|
||||
|
||||
it('returns no common labels on differing sets', () => {
|
||||
expect(findCommonLabels([{ foo: 'bar' }, {}])).toEqual({});
|
||||
expect(findCommonLabels([{}, { foo: 'bar' }])).toEqual({});
|
||||
expect(findCommonLabels([{ baz: '42' }, { foo: 'bar' }])).toEqual({});
|
||||
expect(findCommonLabels([{ foo: '42', baz: 'bar' }, { foo: 'bar' }])).toEqual({});
|
||||
});
|
||||
|
||||
it('returns the single labels set as common labels', () => {
|
||||
expect(findCommonLabels([{ foo: 'bar' }])).toEqual({ foo: 'bar' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUniqueLabels()', () => {
|
||||
it('returns no uncommon labels on empty sets', () => {
|
||||
expect(findUniqueLabels({}, {})).toEqual({});
|
||||
});
|
||||
|
||||
it('returns all labels given no common labels', () => {
|
||||
expect(findUniqueLabels({ foo: '"bar"' }, {})).toEqual({ foo: '"bar"' });
|
||||
});
|
||||
|
||||
it('returns all labels except the common labels', () => {
|
||||
expect(findUniqueLabels({ foo: '"bar"', baz: '"42"' }, { foo: '"bar"' })).toEqual({ baz: '"42"' });
|
||||
});
|
||||
});
|
||||
75
packages/grafana-ui/src/utils/labels.ts
Normal file
75
packages/grafana-ui/src/utils/labels.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Labels } from '../types/data';
|
||||
|
||||
/**
|
||||
* Regexp to extract Prometheus-style labels
|
||||
*/
|
||||
const labelRegexp = /\b(\w+)(!?=~?)"([^"\n]*?)"/g;
|
||||
|
||||
/**
|
||||
* Returns a map of label keys to value from an input selector string.
|
||||
*
|
||||
* Example: `parseLabels('{job="foo", instance="bar"}) // {job: "foo", instance: "bar"}`
|
||||
*/
|
||||
export function parseLabels(labels: string): Labels {
|
||||
const labelsByKey: Labels = {};
|
||||
labels.replace(labelRegexp, (_, key, operator, value) => {
|
||||
labelsByKey[key] = value;
|
||||
return '';
|
||||
});
|
||||
return labelsByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map labels that are common to the given label sets.
|
||||
*/
|
||||
export function findCommonLabels(labelsSets: Labels[]): Labels {
|
||||
return labelsSets.reduce(
|
||||
(acc, labels) => {
|
||||
if (!labels) {
|
||||
throw new Error('Need parsed labels to find common labels.');
|
||||
}
|
||||
if (!acc) {
|
||||
// Initial set
|
||||
acc = { ...labels };
|
||||
} else {
|
||||
// Remove incoming labels that are missing or not matching in value
|
||||
Object.keys(labels).forEach(key => {
|
||||
if (acc[key] === undefined || acc[key] !== labels[key]) {
|
||||
delete acc[key];
|
||||
}
|
||||
});
|
||||
// Remove common labels that are missing from incoming label set
|
||||
Object.keys(acc).forEach(key => {
|
||||
if (labels[key] === undefined) {
|
||||
delete acc[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
(undefined as unknown) as Labels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of labels that are in `labels`, but not in `commonLabels`.
|
||||
*/
|
||||
export function findUniqueLabels(labels: Labels, commonLabels: Labels): Labels {
|
||||
const uncommonLabels: Labels = { ...labels };
|
||||
Object.keys(commonLabels).forEach(key => {
|
||||
delete uncommonLabels[key];
|
||||
});
|
||||
return uncommonLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given labels to a string.
|
||||
*/
|
||||
export function formatLabels(labels: Labels, defaultValue = ''): string {
|
||||
if (!labels || Object.keys(labels).length === 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
const labelKeys = Object.keys(labels).sort();
|
||||
const cleanSelector = labelKeys.map(key => `${key}="${labels[key]}"`).join(', ');
|
||||
return ['{', cleanSelector, '}'].join('');
|
||||
}
|
||||
27
packages/grafana-ui/src/utils/logs.test.ts
Normal file
27
packages/grafana-ui/src/utils/logs.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { LogLevel } from '../types/logs';
|
||||
import { getLogLevel } from './logs';
|
||||
|
||||
describe('getLoglevel()', () => {
|
||||
it('returns no log level on empty line', () => {
|
||||
expect(getLogLevel('')).toBe(LogLevel.unknown);
|
||||
});
|
||||
|
||||
it('returns no log level on when level is part of a word', () => {
|
||||
expect(getLogLevel('this is information')).toBe(LogLevel.unknown);
|
||||
});
|
||||
|
||||
it('returns same log level for long and short version', () => {
|
||||
expect(getLogLevel('[Warn]')).toBe(LogLevel.warning);
|
||||
expect(getLogLevel('[Warning]')).toBe(LogLevel.warning);
|
||||
expect(getLogLevel('[Warn]')).toBe('warning');
|
||||
});
|
||||
|
||||
it('returns log level on line contains a log level', () => {
|
||||
expect(getLogLevel('warn: it is looking bad')).toBe(LogLevel.warn);
|
||||
expect(getLogLevel('2007-12-12 12:12:12 [WARN]: it is looking bad')).toBe(LogLevel.warn);
|
||||
});
|
||||
|
||||
it('returns first log level found', () => {
|
||||
expect(getLogLevel('WARN this could be a debug message')).toBe(LogLevel.warn);
|
||||
});
|
||||
});
|
||||
35
packages/grafana-ui/src/utils/logs.ts
Normal file
35
packages/grafana-ui/src/utils/logs.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { LogLevel } from '../types/logs';
|
||||
import { SeriesData, FieldType } from '../types/data';
|
||||
|
||||
/**
|
||||
* Returns the log level of a log line.
|
||||
* Parse the line for level words. If no level is found, it returns `LogLevel.unknown`.
|
||||
*
|
||||
* Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
|
||||
*/
|
||||
export function getLogLevel(line: string): LogLevel {
|
||||
if (!line) {
|
||||
return LogLevel.unknown;
|
||||
}
|
||||
for (const key of Object.keys(LogLevel)) {
|
||||
const regexp = new RegExp(`\\b${key}\\b`, 'i');
|
||||
if (regexp.test(line)) {
|
||||
const level = (LogLevel as any)[key];
|
||||
if (level) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
}
|
||||
return LogLevel.unknown;
|
||||
}
|
||||
|
||||
export function addLogLevelToSeries(series: SeriesData, lineIndex: number): SeriesData {
|
||||
return {
|
||||
...series, // Keeps Tags, RefID etc
|
||||
fields: [...series.fields, { name: 'LogLevel', type: FieldType.string }],
|
||||
rows: series.rows.map(row => {
|
||||
const line = row[lineIndex];
|
||||
return [...row, getLogLevel(line)];
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export function guessFieldTypeFromValue(v: any): FieldType {
|
||||
/**
|
||||
* Looks at the data to guess the column type. This ignores any existing setting
|
||||
*/
|
||||
function guessFieldTypeFromTable(series: SeriesData, index: number): FieldType | undefined {
|
||||
export function guessFieldTypeFromSeries(series: SeriesData, index: number): FieldType | undefined {
|
||||
const column = series.fields[index];
|
||||
|
||||
// 1. Use the column name to guess
|
||||
@@ -129,7 +129,7 @@ export const guessFieldTypes = (series: SeriesData): SeriesData => {
|
||||
// Replace it with a calculated version
|
||||
return {
|
||||
...field,
|
||||
type: guessFieldTypeFromTable(series, index),
|
||||
type: guessFieldTypeFromSeries(series, index),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -162,7 +162,7 @@ export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData
|
||||
const { fields, rows } = series;
|
||||
|
||||
if (fields.length === 2) {
|
||||
const type = guessFieldTypeFromTable(series, 1);
|
||||
const type = guessFieldTypeFromSeries(series, 1);
|
||||
if (type === FieldType.time) {
|
||||
return {
|
||||
target: fields[0].name || series.name,
|
||||
|
||||
Reference in New Issue
Block a user