mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
fix(logging): default to pretty logging [EE-4371] (#7847)
* fix(logging): default to pretty logging EE-4371 * feat(app/logs): prettify stack traces in JSON logs * feat(nomad/logs): prettify JSON logs in log viewer * feat(kubernetes/logs): prettigy JSON logs in log viewers * feat(app/logs): format and color zerolog prettified logs * fix(app/logs): pre-parse logs when they are double serialized Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com> Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
This commit is contained in:
parent
ee5600b6af
commit
535a26412f
27 changed files with 935 additions and 279 deletions
141
app/docker/helpers/logHelper/formatLogs.ts
Normal file
141
app/docker/helpers/logHelper/formatLogs.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
import tokenize from '@nxmix/tokenize-ansi';
|
||||
import { FontWeight } from 'xterm';
|
||||
|
||||
import {
|
||||
colors,
|
||||
BACKGROUND_COLORS_BY_ANSI,
|
||||
FOREGROUND_COLORS_BY_ANSI,
|
||||
RGBColor,
|
||||
} from './colors';
|
||||
import { formatJSONLine } from './formatJSONLogs';
|
||||
import { formatZerologLogs, ZerologRegex } from './formatZerologLogs';
|
||||
import { Token, Span, TIMESTAMP_LENGTH, FormattedLine } from './types';
|
||||
|
||||
type FormatOptions = {
|
||||
stripHeaders?: boolean;
|
||||
withTimestamps?: boolean;
|
||||
splitter?: string;
|
||||
};
|
||||
|
||||
const defaultOptions: FormatOptions = {
|
||||
splitter: '\n',
|
||||
};
|
||||
|
||||
export function formatLogs(
|
||||
rawLogs: string,
|
||||
{
|
||||
stripHeaders,
|
||||
withTimestamps,
|
||||
splitter = '\n',
|
||||
}: FormatOptions = defaultOptions
|
||||
) {
|
||||
let logs = rawLogs;
|
||||
if (stripHeaders) {
|
||||
logs = stripHeadersFunc(logs);
|
||||
}
|
||||
if (logs.includes('\\n')) {
|
||||
logs = JSON.parse(logs);
|
||||
}
|
||||
|
||||
const tokens: Token[][] = tokenize(logs);
|
||||
const formattedLogs: FormattedLine[] = [];
|
||||
|
||||
let fgColor: string | undefined;
|
||||
let bgColor: string | undefined;
|
||||
let fontWeight: FontWeight | undefined;
|
||||
let line = '';
|
||||
let spans: Span[] = [];
|
||||
|
||||
tokens.forEach((token) => {
|
||||
const [type] = token;
|
||||
|
||||
const fgAnsi = FOREGROUND_COLORS_BY_ANSI[type];
|
||||
const bgAnsi = BACKGROUND_COLORS_BY_ANSI[type];
|
||||
|
||||
if (fgAnsi) {
|
||||
fgColor = cssColorFromRgb(fgAnsi);
|
||||
} else if (type === 'moreColor') {
|
||||
fgColor = extendedColorForToken(token);
|
||||
} else if (type === 'fgDefault') {
|
||||
fgColor = undefined;
|
||||
} else if (bgAnsi) {
|
||||
bgColor = cssColorFromRgb(bgAnsi);
|
||||
} else if (type === 'bgMoreColor') {
|
||||
bgColor = extendedColorForToken(token);
|
||||
} else if (type === 'bgDefault') {
|
||||
bgColor = undefined;
|
||||
} else if (type === 'reset') {
|
||||
fgColor = undefined;
|
||||
bgColor = undefined;
|
||||
fontWeight = undefined;
|
||||
} else if (type === 'bold') {
|
||||
fontWeight = 'bold';
|
||||
} else if (type === 'normal') {
|
||||
fontWeight = 'normal';
|
||||
} else if (type === 'text') {
|
||||
const tokenLines = (token[1] as string).split(splitter);
|
||||
|
||||
tokenLines.forEach((tokenLine, idx) => {
|
||||
if (idx && line) {
|
||||
formattedLogs.push({ line, spans });
|
||||
line = '';
|
||||
spans = [];
|
||||
}
|
||||
|
||||
const text = stripEscapeCodes(tokenLine);
|
||||
if (
|
||||
(!withTimestamps && text.startsWith('{')) ||
|
||||
(withTimestamps && text.substring(TIMESTAMP_LENGTH).startsWith('{'))
|
||||
) {
|
||||
const lines = formatJSONLine(text, withTimestamps);
|
||||
formattedLogs.push(...lines);
|
||||
} else if (ZerologRegex.test(text)) {
|
||||
const lines = formatZerologLogs(text, withTimestamps);
|
||||
formattedLogs.push(...lines);
|
||||
} else {
|
||||
spans.push({ fgColor, bgColor, text, fontWeight });
|
||||
line += text;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (line) {
|
||||
formattedLogs.push({ line, spans });
|
||||
}
|
||||
|
||||
return formattedLogs;
|
||||
}
|
||||
|
||||
function stripHeadersFunc(logs: string) {
|
||||
return logs.substring(8).replace(/\r?\n(.{8})/g, '\n');
|
||||
}
|
||||
|
||||
function stripEscapeCodes(logs: string) {
|
||||
return logs.replace(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function cssColorFromRgb(rgb: RGBColor) {
|
||||
const [r, g, b] = rgb;
|
||||
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
// assuming types based on original JS implementation
|
||||
// there is not much type definitions for the tokenize library
|
||||
function extendedColorForToken(token: Token[]) {
|
||||
const [, colorMode, colorRef] = token as [undefined, number, number];
|
||||
|
||||
if (colorMode === 2) {
|
||||
return cssColorFromRgb(token.slice(2) as RGBColor);
|
||||
}
|
||||
|
||||
if (colorMode === 5 && colors[colorRef]) {
|
||||
return cssColorFromRgb(colors[colorRef]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue