portainer/app/docker/helpers/logHelper/formatZerologLogs.ts

126 lines
3.0 KiB
TypeScript
Raw Normal View History

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;
if (withTimestamps) {
const timestamp = rawText.substring(0, TIMESTAMP_LENGTH);
spans.push({ text: timestamp });
line += `${timestamp} `;
}
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];
}