mirror of https://github.com/portainer/portainer
224 lines
6.8 KiB
JavaScript
224 lines
6.8 KiB
JavaScript
import tokenize from '@nxmix/tokenize-ansi';
|
|
import x256 from 'x256';
|
|
import { takeRight, without } from 'lodash';
|
|
import { format } from 'date-fns';
|
|
|
|
const FOREGROUND_COLORS_BY_ANSI = {
|
|
black: x256.colors[0],
|
|
red: x256.colors[1],
|
|
green: x256.colors[2],
|
|
yellow: x256.colors[3],
|
|
blue: x256.colors[4],
|
|
magenta: x256.colors[5],
|
|
cyan: x256.colors[6],
|
|
white: x256.colors[7],
|
|
brightBlack: x256.colors[8],
|
|
brightRed: x256.colors[9],
|
|
brightGreen: x256.colors[10],
|
|
brightYellow: x256.colors[11],
|
|
brightBlue: x256.colors[12],
|
|
brightMagenta: x256.colors[13],
|
|
brightCyan: x256.colors[14],
|
|
brightWhite: x256.colors[15],
|
|
};
|
|
|
|
const BACKGROUND_COLORS_BY_ANSI = {
|
|
bgBlack: x256.colors[0],
|
|
bgRed: x256.colors[1],
|
|
bgGreen: x256.colors[2],
|
|
bgYellow: x256.colors[3],
|
|
bgBlue: x256.colors[4],
|
|
bgMagenta: x256.colors[5],
|
|
bgCyan: x256.colors[6],
|
|
bgWhite: x256.colors[7],
|
|
bgBrightBlack: x256.colors[8],
|
|
bgBrightRed: x256.colors[9],
|
|
bgBrightGreen: x256.colors[10],
|
|
bgBrightYellow: x256.colors[11],
|
|
bgBrightBlue: x256.colors[12],
|
|
bgBrightMagenta: x256.colors[13],
|
|
bgBrightCyan: x256.colors[14],
|
|
bgBrightWhite: x256.colors[15],
|
|
};
|
|
|
|
const TIMESTAMP_LENGTH = 31; // 30 for timestamp + 1 for trailing space
|
|
|
|
angular.module('portainer.docker').factory('LogHelper', [
|
|
function LogHelperFactory() {
|
|
'use strict';
|
|
var helper = {};
|
|
|
|
function stripHeaders(logs) {
|
|
logs = logs.substring(8);
|
|
logs = logs.replace(/\r?\n(.{8})/g, '\n');
|
|
|
|
return logs;
|
|
}
|
|
|
|
function stripEscapeCodes(logs) {
|
|
return logs.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
}
|
|
|
|
function cssColorFromRgb(rgb) {
|
|
const [r, g, b] = rgb;
|
|
|
|
return `rgb(${r}, ${g}, ${b})`;
|
|
}
|
|
|
|
function extendedColorForToken(token) {
|
|
const colorMode = token[1];
|
|
|
|
if (colorMode === 2) {
|
|
return cssColorFromRgb(token.slice(2));
|
|
}
|
|
|
|
if (colorMode === 5 && x256.colors[token[2]]) {
|
|
return cssColorFromRgb(x256.colors[token[2]]);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// Return an array with each log including a line and styled spans for each entry.
|
|
// If the stripHeaders param is specified, it will strip the 8 first characters of each line.
|
|
// withTimestamps param is needed to find the start of JSON for Zerolog logs parsing
|
|
helper.formatLogs = function (logs, { stripHeaders: skipHeaders, withTimestamps }) {
|
|
if (skipHeaders) {
|
|
logs = stripHeaders(logs);
|
|
}
|
|
|
|
const tokens = tokenize(logs);
|
|
const formattedLogs = [];
|
|
|
|
let foregroundColor = null;
|
|
let backgroundColor = null;
|
|
let line = '';
|
|
let spans = [];
|
|
|
|
for (const token of tokens) {
|
|
const type = token[0];
|
|
|
|
if (FOREGROUND_COLORS_BY_ANSI[type]) {
|
|
foregroundColor = cssColorFromRgb(FOREGROUND_COLORS_BY_ANSI[type]);
|
|
} else if (type === 'moreColor') {
|
|
foregroundColor = extendedColorForToken(token);
|
|
} else if (type === 'fgDefault') {
|
|
foregroundColor = null;
|
|
} else if (BACKGROUND_COLORS_BY_ANSI[type]) {
|
|
backgroundColor = cssColorFromRgb(BACKGROUND_COLORS_BY_ANSI[type]);
|
|
} else if (type === 'bgMoreColor') {
|
|
backgroundColor = extendedColorForToken(token);
|
|
} else if (type === 'bgDefault') {
|
|
backgroundColor = null;
|
|
} else if (type === 'reset') {
|
|
foregroundColor = null;
|
|
backgroundColor = null;
|
|
} else if (type === 'text') {
|
|
const tokenLines = token[1].split('\n');
|
|
|
|
for (let i = 0; i < tokenLines.length; i++) {
|
|
if (i !== 0) {
|
|
formattedLogs.push({ line, spans });
|
|
|
|
line = '';
|
|
spans = [];
|
|
}
|
|
|
|
const text = stripEscapeCodes(tokenLines[i]);
|
|
if ((!withTimestamps && text.startsWith('{')) || (withTimestamps && text.substring(TIMESTAMP_LENGTH).startsWith('{'))) {
|
|
line += JSONToFormattedLine(text, spans, withTimestamps);
|
|
} else {
|
|
spans.push({ foregroundColor, backgroundColor, text });
|
|
line += text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (line) {
|
|
formattedLogs.push({ line, spans });
|
|
}
|
|
|
|
return formattedLogs;
|
|
};
|
|
|
|
return helper;
|
|
},
|
|
]);
|
|
|
|
const JSONColors = {
|
|
Grey: 'var(--text-log-viewer-color-json-grey)',
|
|
Magenta: 'var(--text-log-viewer-color-json-magenta)',
|
|
Yellow: 'var(--text-log-viewer-color-json-yellow)',
|
|
Green: 'var(--text-log-viewer-color-json-green)',
|
|
Red: 'var(--text-log-viewer-color-json-red)',
|
|
Blue: 'var(--text-log-viewer-color-json-blue)',
|
|
};
|
|
|
|
const spaceSpan = { text: ' ' };
|
|
|
|
function logLevelToSpan(level) {
|
|
switch (level) {
|
|
case 'debug':
|
|
return { foregroundColor: JSONColors.Grey, text: 'DBG', fontWeight: 'bold' };
|
|
case 'info':
|
|
return { foregroundColor: JSONColors.Green, text: 'INF', fontWeight: 'bold' };
|
|
case 'warn':
|
|
return { foregroundColor: JSONColors.Yellow, text: 'WRN', fontWeight: 'bold' };
|
|
case 'error':
|
|
return { foregroundColor: JSONColors.Red, text: 'ERR', fontWeight: 'bold' };
|
|
default:
|
|
return { text: level };
|
|
}
|
|
}
|
|
|
|
function JSONToFormattedLine(rawText, spans, withTimestamps) {
|
|
const text = withTimestamps ? rawText.substring(TIMESTAMP_LENGTH) : rawText;
|
|
const json = JSON.parse(text);
|
|
const { level, caller, message, time } = json;
|
|
let line = '';
|
|
|
|
if (withTimestamps) {
|
|
const timestamp = rawText.substring(0, TIMESTAMP_LENGTH);
|
|
spans.push({ text: timestamp });
|
|
line += `${timestamp}`;
|
|
}
|
|
if (time) {
|
|
const date = format(new Date(time * 1000), 'Y/MM/dd hh:mmaa');
|
|
spans.push({ foregroundColor: JSONColors.Grey, text: date }, spaceSpan);
|
|
line += `${date} `;
|
|
}
|
|
if (level) {
|
|
const levelSpan = logLevelToSpan(level);
|
|
spans.push(levelSpan, spaceSpan);
|
|
line += `${levelSpan.text} `;
|
|
}
|
|
if (caller) {
|
|
const trimmedCaller = takeRight(caller.split('/'), 2).join('/');
|
|
spans.push({ foregroundColor: JSONColors.Magenta, text: trimmedCaller, fontWeight: 'bold' }, spaceSpan);
|
|
spans.push({ foregroundColor: JSONColors.Blue, text: '>' }, spaceSpan);
|
|
line += `${trimmedCaller} > `;
|
|
}
|
|
|
|
const keys = without(Object.keys(json), 'time', 'level', 'caller', 'message');
|
|
if (message) {
|
|
spans.push({ foregroundColor: JSONColors.Magenta, text: `${message}` }, spaceSpan);
|
|
line += `${message} `;
|
|
|
|
if (keys.length) {
|
|
spans.push({ foregroundColor: JSONColors.Magenta, text: `|` }, spaceSpan);
|
|
line += '| ';
|
|
}
|
|
}
|
|
|
|
keys.forEach((key) => {
|
|
const value = json[key];
|
|
spans.push({ foregroundColor: JSONColors.Blue, text: `${key}=` });
|
|
spans.push({ foregroundColor: key === 'error' ? JSONColors.Red : JSONColors.Magenta, text: value });
|
|
spans.push(spaceSpan);
|
|
line += `${key}=${value} `;
|
|
});
|
|
|
|
return line;
|
|
}
|