2022-07-28 15:07:35 +02:00
|
|
|
import {isLogLevel, isUndefined, LoggingConfig, LogLevel, LogLevels} from "./types";
|
2022-02-09 18:01:44 +01:00
|
|
|
import {ActivatableLoggerImpl} from "./logger";
|
|
|
|
|
2022-07-28 15:07:35 +02:00
|
|
|
function withDefault<T>(value: T | undefined, defaultValue: T): T {
|
|
|
|
return isUndefined(value) ? defaultValue : value;
|
|
|
|
}
|
|
|
|
|
2022-02-09 18:01:44 +01:00
|
|
|
class TestableLogger extends ActivatableLoggerImpl {
|
|
|
|
private logs: any[][] = [];
|
|
|
|
|
2022-07-28 15:07:35 +02:00
|
|
|
constructor(
|
|
|
|
enabled?: boolean,
|
|
|
|
debug?: boolean,
|
|
|
|
profile?: boolean,
|
|
|
|
) {
|
2022-02-09 18:01:44 +01:00
|
|
|
super();
|
|
|
|
this.init(
|
2022-07-28 15:07:35 +02:00
|
|
|
new LoggingConfig(
|
|
|
|
withDefault(enabled, true),
|
|
|
|
withDefault(debug, true),
|
|
|
|
withDefault(profile, true),
|
|
|
|
),
|
2022-02-09 18:01:44 +01:00
|
|
|
(...args: any[]): void => this.doLog(...args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
doLog(...args: any[]): void {
|
|
|
|
this.logs.push(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
getLogs(): any[][] {
|
|
|
|
return this.logs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ParsedLogEntry = {
|
|
|
|
level: LogLevel,
|
|
|
|
tags: string[],
|
|
|
|
message: string,
|
|
|
|
args: any[],
|
|
|
|
};
|
|
|
|
|
|
|
|
function parseLogEntry(logEntry: any[]): ParsedLogEntry {
|
|
|
|
if (!logEntry.length) {
|
|
|
|
throw new Error(
|
|
|
|
`Empty log entry. Should always start with log message: ${logEntry}`
|
|
|
|
);
|
|
|
|
}
|
2022-07-28 13:16:13 +02:00
|
|
|
|
2022-02-09 18:01:44 +01:00
|
|
|
const logMessage = logEntry[0];
|
|
|
|
if (typeof logMessage !== "string") {
|
|
|
|
throw new Error(
|
|
|
|
`Expected log entry to start with string, but got: ${logMessage}`
|
|
|
|
);
|
2022-07-28 13:16:13 +02:00
|
|
|
}
|
2022-02-09 18:01:44 +01:00
|
|
|
|
2022-07-28 15:07:35 +02:00
|
|
|
// noinspection RegExpRedundantEscape
|
2022-02-09 18:01:44 +01:00
|
|
|
const regexp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ([A-Z]+) - (\[[^\]]*\])? *(.*)$/;
|
|
|
|
const groups = logMessage.match(regexp);
|
|
|
|
if (groups === null || groups.length < 4) {
|
|
|
|
throw new Error(
|
|
|
|
`Malformed log message.\n\nExpected format: ${regexp}.\nGot: ${logMessage}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const level = groups[1].toLowerCase();
|
|
|
|
if (!isLogLevel(level)) {
|
|
|
|
throw new Error(
|
|
|
|
`Unknown log level "${level}" in log message: ${logMessage}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const tagsStr = groups[2].substring(1, groups[2].length - 1);
|
2022-07-28 13:16:13 +02:00
|
|
|
const tags = tagsStr ? tagsStr.split(", "): [];
|
2022-02-09 18:01:44 +01:00
|
|
|
const message = groups[3];
|
|
|
|
const args = logEntry.slice(1);
|
|
|
|
|
|
|
|
return {
|
|
|
|
level: level as LogLevel,
|
2022-07-28 13:16:13 +02:00
|
|
|
tags,
|
2022-02-09 18:01:44 +01:00
|
|
|
message,
|
|
|
|
args,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseLogs(logs: any[][]): ParsedLogEntry[] {
|
|
|
|
const parsedLogs: ParsedLogEntry[] = [];
|
|
|
|
for (const logEntry of logs) {
|
|
|
|
parsedLogs.push(parseLogEntry(logEntry));
|
|
|
|
}
|
|
|
|
return parsedLogs;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const level of LogLevels) {
|
|
|
|
test(`should log single untagged ${level} message without parameters`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag()[level]("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: [],
|
|
|
|
message: "message",
|
|
|
|
args: [],
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should log single tagged ${level} message without parameters`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag1", "tag2")[level]("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: ["tag1", "tag2"],
|
|
|
|
message: "message",
|
|
|
|
args: [],
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should log single tagged ${level} message with parameters`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag1", "tag2")[level]("message", 1, {}, [false]);
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: ["tag1", "tag2"],
|
|
|
|
message: "message",
|
|
|
|
args: [1, {}, [false]],
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should escape tags for ${level} message without parameters`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("%s", "%d", "%f", "%o", "%")[level]("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: ["%%s", "%%d", "%%f", "%%o", "%%"],
|
|
|
|
message: "message",
|
|
|
|
args: [],
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should not escape ${level} message itself`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag")[level]("%s %d %f %o %%");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: ["tag"],
|
|
|
|
message: "%s %d %f %o %%",
|
|
|
|
args: [],
|
|
|
|
}]);
|
2022-07-28 13:16:13 +02:00
|
|
|
});
|
2022-02-09 18:01:44 +01:00
|
|
|
|
|
|
|
test(`should not escape ${level} message arguments`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger();
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag")[level]("message", 1, "%s", "%d", "%f", "%o", "%");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level,
|
|
|
|
tags: ["tag"],
|
|
|
|
message: "message",
|
|
|
|
args: [1, "%s", "%d", "%f", "%o", "%"],
|
|
|
|
}]);
|
2022-07-28 13:16:13 +02:00
|
|
|
});
|
2022-02-09 18:01:44 +01:00
|
|
|
|
|
|
|
test(`should not log ${level} message on disabled logger`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger(false);
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag")[level]("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([]);
|
2022-07-28 13:16:13 +02:00
|
|
|
});
|
2022-02-09 18:01:44 +01:00
|
|
|
}
|
|
|
|
|
2022-07-28 15:07:35 +02:00
|
|
|
test(`should not log debug message with disabled debugging`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger(true, false, true);
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag").debug("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should log profile message with disabled debugging`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger(true, false, true);
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag").profile("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level: "profile",
|
|
|
|
tags: ["tag"],
|
|
|
|
message: "message",
|
|
|
|
args: [],
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should not log profile message with disabled profiling`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger(true, true, false);
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag").profile("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(`should log debug message with disabled profiling`, () => {
|
|
|
|
// given
|
|
|
|
const logger = new TestableLogger(true, true, false);
|
|
|
|
|
|
|
|
// when
|
|
|
|
logger.tag("tag").debug("message");
|
|
|
|
|
|
|
|
// then
|
|
|
|
expect(parseLogs(logger.getLogs())).toEqual([{
|
|
|
|
level: "debug",
|
|
|
|
tags: ["tag"],
|
|
|
|
message: "message",
|
|
|
|
args: [],
|
|
|
|
}]);
|
|
|
|
});
|