feat: show log severity indicator based on severity number if it's available when severity text is unknown (#4971)

* feat: set log sev indicator based on severity number if severity text is unknown

* chore: some cleanup

* chore: some more cleanup

* chore: update log state indicator utils test

* chore: some more cleanup

* fix: priority to severity_number over severity_text and update tests

* fix: made the severity_text check case insensitive and added null checks

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
This commit is contained in:
Raj Kamal Singh
2024-08-01 12:06:29 +05:30
committed by GitHub
parent a8e4359d95
commit 3783ffdd4c
5 changed files with 184 additions and 25 deletions

View File

@@ -28,12 +28,17 @@ export const SEVERITY_TEXT_TYPE = {
FATAL2: 'FATAL2',
FATAL3: 'FATAL3',
FATAL4: 'FATAL4',
UNKNOWN: 'UNKNOWN',
} as const;
export const LogType = {
TRACE: 'TRACE',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARNING: 'WARNING',
WARN: 'WARN',
ERROR: 'ERROR',
FATAL: 'FATAL',
UNKNOWN: 'UNKNOWN',
} as const;
function LogStateIndicator({

View File

@@ -1,9 +1,10 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ILog } from 'types/api/logs/log';
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';
describe('getLogIndicatorType', () => {
it('should return severity type for valid log with severityText', () => {
it('severity_number should be given priority over severity_text', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
@@ -20,11 +21,57 @@ describe('getLogIndicatorType', () => {
attributesInt: {},
attributesFloat: {},
severity_text: 'INFO',
severity_number: 2,
};
expect(getLogIndicatorType(log)).toBe('INFO');
// severity_number should get priority over severity_text
expect(getLogIndicatorType(log)).toBe('TRACE');
});
it('should return log level if severityText is missing', () => {
it('severity_text should be used when severity_number is absent ', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'FATAL',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('case insensitive severity_text should be valid', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'fatAl',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('should return log level if severityText and severityNumber is missing', () => {
const log: ILog = {
date: '2024-02-29T12:34:58Z',
timestamp: 1646115296,
@@ -36,13 +83,16 @@ describe('getLogIndicatorType', () => {
body: 'Sample log',
resources_string: {},
attributesString: {},
attributes_string: {},
attributes_string: {
log_level: 'INFO' as never,
},
attributesInt: {},
attributesFloat: {},
severity_text: 'FATAL',
severity_text: 'some_random',
severityText: '',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
expect(getLogIndicatorType(log)).toBe('INFO');
});
});
@@ -55,6 +105,7 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 2,
severity_number: 2,
body: 'Sample log message',
resources_string: {},
@@ -64,7 +115,7 @@ describe('getLogIndicatorTypeForTable', () => {
attributesFloat: {},
severity_text: 'WARN',
};
expect(getLogIndicatorTypeForTable(log)).toBe('WARN');
expect(getLogIndicatorTypeForTable(log)).toBe('TRACE');
});
it('should return log level if severityText is missing', () => {
@@ -75,7 +126,8 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 2,
severityNumber: 0,
severity_number: 0,
body: 'Sample log message',
resources_string: {},
attributesString: {},
@@ -87,3 +139,47 @@ describe('getLogIndicatorTypeForTable', () => {
expect(getLogIndicatorTypeForTable(log)).toBe('INFO');
});
});
describe('logIndicatorBySeverityNumber', () => {
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const logLevelExpectations = [
{ minSevNumber: 1, maxSevNumber: 4, expectedIndicatorType: 'TRACE' },
{ minSevNumber: 5, maxSevNumber: 8, expectedIndicatorType: 'DEBUG' },
{ minSevNumber: 9, maxSevNumber: 12, expectedIndicatorType: 'INFO' },
{ minSevNumber: 13, maxSevNumber: 16, expectedIndicatorType: 'WARN' },
{ minSevNumber: 17, maxSevNumber: 20, expectedIndicatorType: 'ERROR' },
{ minSevNumber: 21, maxSevNumber: 24, expectedIndicatorType: 'FATAL' },
];
logLevelExpectations.forEach((e) => {
for (let sevNum = e.minSevNumber; sevNum <= e.maxSevNumber; sevNum++) {
const sevText = (Math.random() + 1).toString(36).substring(2);
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: sevText,
severityNumber: sevNum,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: sevText,
severity_number: sevNum,
};
it(`getLogIndicatorType should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorType(log)).toBe(e.expectedIndicatorType);
});
it(`getLogIndicatorTypeForTable should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorTypeForTable(log)).toBe(e.expectedIndicatorType);
});
}
});
});

View File

@@ -2,56 +2,112 @@ import { ILog } from 'types/api/logs/log';
import { LogType, SEVERITY_TEXT_TYPE } from './LogStateIndicator';
const getSeverityType = (severityText: string): string => {
const getLogTypeBySeverityText = (severityText: string): string => {
switch (severityText) {
case SEVERITY_TEXT_TYPE.TRACE:
case SEVERITY_TEXT_TYPE.TRACE2:
case SEVERITY_TEXT_TYPE.TRACE3:
case SEVERITY_TEXT_TYPE.TRACE4:
return SEVERITY_TEXT_TYPE.TRACE;
return LogType.TRACE;
case SEVERITY_TEXT_TYPE.DEBUG:
case SEVERITY_TEXT_TYPE.DEBUG2:
case SEVERITY_TEXT_TYPE.DEBUG3:
case SEVERITY_TEXT_TYPE.DEBUG4:
return SEVERITY_TEXT_TYPE.DEBUG;
return LogType.DEBUG;
case SEVERITY_TEXT_TYPE.INFO:
case SEVERITY_TEXT_TYPE.INFO2:
case SEVERITY_TEXT_TYPE.INFO3:
case SEVERITY_TEXT_TYPE.INFO4:
return SEVERITY_TEXT_TYPE.INFO;
return LogType.INFO;
case SEVERITY_TEXT_TYPE.WARN:
case SEVERITY_TEXT_TYPE.WARN2:
case SEVERITY_TEXT_TYPE.WARN3:
case SEVERITY_TEXT_TYPE.WARN4:
case SEVERITY_TEXT_TYPE.WARNING:
return SEVERITY_TEXT_TYPE.WARN;
return LogType.WARN;
case SEVERITY_TEXT_TYPE.ERROR:
case SEVERITY_TEXT_TYPE.ERROR2:
case SEVERITY_TEXT_TYPE.ERROR3:
case SEVERITY_TEXT_TYPE.ERROR4:
return SEVERITY_TEXT_TYPE.ERROR;
return LogType.ERROR;
case SEVERITY_TEXT_TYPE.FATAL:
case SEVERITY_TEXT_TYPE.FATAL2:
case SEVERITY_TEXT_TYPE.FATAL3:
case SEVERITY_TEXT_TYPE.FATAL4:
return SEVERITY_TEXT_TYPE.FATAL;
return LogType.FATAL;
default:
return SEVERITY_TEXT_TYPE.INFO;
return LogType.UNKNOWN;
}
};
export const getLogIndicatorType = (logData: ILog): string => {
if (logData.severity_text) {
return getSeverityType(logData.severity_text);
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const getLogTypeBySeverityNumber = (severityNumber: number): string => {
if (severityNumber < 1) {
return LogType.UNKNOWN;
}
return logData.attributes_string?.log_level || LogType.INFO;
if (severityNumber < 5) {
return LogType.TRACE;
}
if (severityNumber < 9) {
return LogType.DEBUG;
}
if (severityNumber < 13) {
return LogType.INFO;
}
if (severityNumber < 17) {
return LogType.WARN;
}
if (severityNumber < 21) {
return LogType.ERROR;
}
if (severityNumber < 25) {
return LogType.FATAL;
}
return LogType.UNKNOWN;
};
const getLogType = (
severityText: string,
severityNumber: number,
defaultType: string,
): string => {
// give priority to the severityNumber
if (severityNumber) {
const logType = getLogTypeBySeverityNumber(severityNumber);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
// is severityNumber is not present then rely on the severityText
if (severityText) {
const logType = getLogTypeBySeverityText(severityText);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
return defaultType;
};
export const getLogIndicatorType = (logData: ILog): string => {
const defaultType = logData.attributes_string?.log_level || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
logData?.severity_text?.toUpperCase(),
logData?.severity_number || 0,
defaultType,
);
};
export const getLogIndicatorTypeForTable = (
log: Record<string, unknown>,
): string => {
if (log.severity_text) {
return getSeverityType(log.severity_text as string);
}
return (log.log_level as string) || LogType.INFO;
const defaultType = (log.log_level as string) || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
(log?.severity_text as string)?.toUpperCase(),
(log?.severity_number as number) || 0,
defaultType,
);
};

View File

@@ -188,6 +188,7 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
attributes: {},
resources: {},
severity_text: logData.severity_text,
severity_number: logData.severity_number,
};
Object.keys(logData).forEach((key) => {

View File

@@ -14,6 +14,7 @@ export interface ILog {
attributesInt: Record<string, never>;
attributesFloat: Record<string, never>;
severity_text: string;
severity_number: number;
}
type OmitAttributesResources = Pick<