2022-10-20 14:33:54 +00:00
|
|
|
import {
|
|
|
|
formatCaller,
|
|
|
|
formatKeyValuePair,
|
|
|
|
formatLevel,
|
|
|
|
formatMessage,
|
|
|
|
formatStackTrace,
|
|
|
|
formatTime,
|
|
|
|
} from './formatters';
|
|
|
|
import {
|
|
|
|
FormattedLine,
|
|
|
|
JSONStackTrace,
|
|
|
|
Level,
|
|
|
|
Span,
|
|
|
|
TIMESTAMP_LENGTH,
|
|
|
|
} from './types';
|
|
|
|
|
|
|
|
const dateRegex = /(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}[AP]M) /; // "2022/02/01 04:30AM "
|
|
|
|
const levelRegex = /(\w{3}) /; // "INF " or "ERR "
|
|
|
|
const callerRegex = /(.+?.go:\d+) /; // "path/to/file.go:line "
|
|
|
|
const chevRegex = /> /; // "> "
|
|
|
|
const messageAndPairsRegex = /(.*)/; // include the rest of the string in a separate group
|
|
|
|
|
|
|
|
const keyRegex = /(\S+=)/g; // ""
|
|
|
|
|
|
|
|
export const ZerologRegex = concatRegex(
|
|
|
|
dateRegex,
|
|
|
|
levelRegex,
|
|
|
|
callerRegex,
|
|
|
|
chevRegex,
|
|
|
|
messageAndPairsRegex
|
|
|
|
);
|
|
|
|
|
|
|
|
function concatRegex(...regs: RegExp[]) {
|
|
|
|
const flags = Array.from(
|
|
|
|
new Set(
|
|
|
|
regs
|
|
|
|
.map((r) => r.flags)
|
|
|
|
.join('')
|
|
|
|
.split('')
|
|
|
|
)
|
|
|
|
).join('');
|
|
|
|
const source = regs.map((r) => r.source).join('');
|
|
|
|
return new RegExp(source, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
type Pair = {
|
|
|
|
key: string;
|
|
|
|
value: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function formatZerologLogs(rawText: string, withTimestamps?: boolean) {
|
|
|
|
const spans: Span[] = [];
|
|
|
|
const lines: FormattedLine[] = [];
|
|
|
|
let line = '';
|
|
|
|
|
|
|
|
const text = withTimestamps ? rawText.substring(TIMESTAMP_LENGTH) : rawText;
|
|
|
|
|
2022-11-04 12:58:18 +00:00
|
|
|
if (withTimestamps) {
|
|
|
|
const timestamp = rawText.substring(0, TIMESTAMP_LENGTH);
|
|
|
|
spans.push({ text: timestamp });
|
|
|
|
line += `${timestamp} `;
|
|
|
|
}
|
|
|
|
|
2022-10-20 14:33:54 +00:00
|
|
|
const [, date, level, caller, messageAndPairs] =
|
|
|
|
text.match(ZerologRegex) || [];
|
|
|
|
|
|
|
|
const [message, pairs] = extractPairs(messageAndPairs);
|
|
|
|
|
|
|
|
line += formatTime(date, spans, line);
|
|
|
|
line += formatLevel(level as Level, spans, line);
|
|
|
|
line += formatCaller(caller, spans, line);
|
|
|
|
line += formatMessage(message, spans, line, !!pairs.length);
|
|
|
|
|
|
|
|
let stackTrace: JSONStackTrace | undefined;
|
|
|
|
const stackTraceIndex = pairs.findIndex((p) => p.key === 'stack_trace');
|
|
|
|
|
|
|
|
if (stackTraceIndex !== -1) {
|
|
|
|
stackTrace = JSON.parse(pairs[stackTraceIndex].value);
|
|
|
|
pairs.splice(stackTraceIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
pairs.forEach(({ key, value }, idx) => {
|
|
|
|
line += formatKeyValuePair(
|
|
|
|
key,
|
|
|
|
value,
|
|
|
|
spans,
|
|
|
|
line,
|
|
|
|
idx === pairs.length - 1
|
|
|
|
);
|
|
|
|
});
|
|
|
|
lines.push({ line, spans });
|
|
|
|
|
|
|
|
formatStackTrace(stackTrace, lines);
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractPairs(messageAndPairs: string): [string, Pair[]] {
|
|
|
|
const pairs: Pair[] = [];
|
|
|
|
let [message, rawPairs] = messageAndPairs.split('|');
|
|
|
|
|
|
|
|
if (!messageAndPairs.includes('|') && !rawPairs) {
|
|
|
|
rawPairs = message;
|
|
|
|
message = '';
|
|
|
|
}
|
|
|
|
message = message.trim();
|
|
|
|
rawPairs = rawPairs.trim();
|
|
|
|
|
|
|
|
const matches = [...rawPairs.matchAll(keyRegex)];
|
|
|
|
|
|
|
|
matches.forEach((m, idx) => {
|
|
|
|
const rawKey = m[0];
|
|
|
|
const key = rawKey.slice(0, -1);
|
|
|
|
const start = m.index || 0;
|
|
|
|
const end = idx !== matches.length - 1 ? matches[idx + 1].index : undefined;
|
|
|
|
const value = (
|
|
|
|
end
|
|
|
|
? rawPairs.slice(start + rawKey.length, end)
|
|
|
|
: rawPairs.slice(start + rawKey.length)
|
|
|
|
).trim();
|
|
|
|
pairs.push({ key, value });
|
|
|
|
});
|
|
|
|
|
|
|
|
return [message, pairs];
|
|
|
|
}
|