mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add winston formatting support for error causes
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
86ac417cab
commit
cd8a6f8dcc
@ -290,7 +290,6 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.5.0",
|
"win-ca": "^3.5.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-console-format": "^1.0.8",
|
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
"ws": "^8.11.0",
|
"ws": "^8.11.0",
|
||||||
"xterm-link-provider": "^1.3.1"
|
"xterm-link-provider": "^1.3.1"
|
||||||
|
|||||||
184
src/common/logger-formaters/console-format.ts
Normal file
184
src/common/logger-formaters/console-format.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { LEVEL, MESSAGE, SPLAT } from "triple-beam";
|
||||||
|
import colors from "colors/safe";
|
||||||
|
import type { InspectOptions } from "util";
|
||||||
|
import { inspect } from "util";
|
||||||
|
|
||||||
|
// The following license was copied from https://github.com/duccio/winston-console-format/blob/master/LICENSE
|
||||||
|
// This was modified to support formatting causes
|
||||||
|
|
||||||
|
/*
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2015 Eugeny Dementev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ConsoleFormatOptions {
|
||||||
|
showMeta?: boolean;
|
||||||
|
metaStrip?: string[];
|
||||||
|
inspectOptions?: InspectOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransformableInfo {
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
[key: string | symbol]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConsoleFormat {
|
||||||
|
private static readonly reSpaces = /^\s+/;
|
||||||
|
private static readonly reSpacesOrEmpty = /^(\s*)/;
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
private static readonly reColor = /\x1B\[\d+m/;
|
||||||
|
private static readonly defaultStrip = [LEVEL, MESSAGE, SPLAT, "level", "message", "ms", "stack"];
|
||||||
|
private static readonly chars = {
|
||||||
|
singleLine: "▪",
|
||||||
|
startLine: "┏",
|
||||||
|
line: "┃",
|
||||||
|
endLine: "┗",
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly showMeta: boolean;
|
||||||
|
private readonly metaStrip: string[];
|
||||||
|
private readonly inspectOptions: InspectOptions;
|
||||||
|
|
||||||
|
public constructor(opts?: ConsoleFormatOptions) {
|
||||||
|
this.showMeta = opts?.showMeta ?? true;
|
||||||
|
this.metaStrip = opts?.metaStrip ?? [];
|
||||||
|
this.inspectOptions = opts?.inspectOptions ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLines(value: unknown): string[] {
|
||||||
|
return inspect(value, this.inspectOptions).split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private message(info: TransformableInfo, chr: string, color: string): string {
|
||||||
|
const message = info.message.replace(
|
||||||
|
ConsoleFormat.reSpacesOrEmpty,
|
||||||
|
`$1${color}${colors.dim(chr)}${colors.reset(" ")}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${info.level}:${message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private pad(message?: string): string {
|
||||||
|
return message?.match(ConsoleFormat.reSpaces)?.[0] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ms(info: TransformableInfo): string {
|
||||||
|
if (info.ms) {
|
||||||
|
return colors.italic(colors.dim(` ${info.ms}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private stack(info: TransformableInfo): string[] {
|
||||||
|
const messages: string[] = [];
|
||||||
|
|
||||||
|
if (info.stack) {
|
||||||
|
const error = new Error();
|
||||||
|
|
||||||
|
error.stack = info.stack;
|
||||||
|
messages.push(...this.getLines(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cause(source: unknown): string[] {
|
||||||
|
const messages: string[] = [];
|
||||||
|
|
||||||
|
if (source instanceof Error && source.cause) {
|
||||||
|
messages.push(`Cause: ${source.cause}`);
|
||||||
|
messages.push(...this.getLines(source.cause).map(l => ` ${l}`));
|
||||||
|
messages.push(...this._cause(source.cause));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private cause(info: TransformableInfo): string[] {
|
||||||
|
const splats = info[SPLAT];
|
||||||
|
|
||||||
|
if (Array.isArray(splats)) {
|
||||||
|
return splats.flatMap(splat => this._cause(splat));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private meta(info: TransformableInfo): string[] {
|
||||||
|
const messages: string[] = [];
|
||||||
|
const stripped = { ...info };
|
||||||
|
|
||||||
|
ConsoleFormat.defaultStrip.forEach((e) => delete stripped[e]);
|
||||||
|
this.metaStrip?.forEach((e) => delete stripped[e]);
|
||||||
|
|
||||||
|
if (Object.keys(stripped).length > 0) {
|
||||||
|
messages.push(...this.getLines(stripped));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getColor(info: TransformableInfo): string {
|
||||||
|
return info.level.match(ConsoleFormat.reColor)?.[0] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private write(info: TransformableInfo, messages: string[], color: string): void {
|
||||||
|
const pad = this.pad(info.message);
|
||||||
|
|
||||||
|
messages.forEach((line, index, arr) => {
|
||||||
|
const lineNumber = colors.dim(`[${(index + 1).toString().padStart(arr.length.toString().length, " ")}]`);
|
||||||
|
let chr = ConsoleFormat.chars.line;
|
||||||
|
|
||||||
|
if (index === arr.length - 1) {
|
||||||
|
chr = ConsoleFormat.chars.endLine;
|
||||||
|
}
|
||||||
|
info[MESSAGE] += `\n${colors.dim(info.level)}:${pad}${color}${colors.dim(chr)}${colors.reset(" ")}`;
|
||||||
|
info[MESSAGE] += `${lineNumber} ${line}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public transform(info: TransformableInfo): TransformableInfo {
|
||||||
|
const messages: string[] = [];
|
||||||
|
|
||||||
|
if (this.showMeta) {
|
||||||
|
messages.push(...this.stack(info));
|
||||||
|
messages.push(...this.meta(info));
|
||||||
|
messages.push(...this.cause(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = this.getColor(info);
|
||||||
|
|
||||||
|
info[MESSAGE] = this.message(info, ConsoleFormat.chars[messages.length > 0 ? "startLine" : "singleLine"], color);
|
||||||
|
info[MESSAGE] += this.ms(info);
|
||||||
|
|
||||||
|
this.write(info, messages, color);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,9 +6,9 @@
|
|||||||
import { app, ipcMain } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
import winston, { format } from "winston";
|
import winston, { format } from "winston";
|
||||||
import type Transport from "winston-transport";
|
import type Transport from "winston-transport";
|
||||||
import { consoleFormat } from "winston-console-format";
|
|
||||||
import { isDebugging, isTestEnv } from "./vars";
|
import { isDebugging, isTestEnv } from "./vars";
|
||||||
import BrowserConsole from "winston-transport-browserconsole";
|
import BrowserConsole from "winston-transport-browserconsole";
|
||||||
|
import { ConsoleFormat } from "./logger-formaters/console-format";
|
||||||
|
|
||||||
export interface Logger {
|
export interface Logger {
|
||||||
info: (message: string, ...args: any) => void;
|
info: (message: string, ...args: any) => void;
|
||||||
@ -37,7 +37,7 @@ if (ipcMain) {
|
|||||||
format.colorize({ level: true, message: false }),
|
format.colorize({ level: true, message: false }),
|
||||||
format.padLevels(),
|
format.padLevels(),
|
||||||
format.ms(),
|
format.ms(),
|
||||||
consoleFormat({
|
new ConsoleFormat({
|
||||||
showMeta: true,
|
showMeta: true,
|
||||||
inspectOptions: {
|
inspectOptions: {
|
||||||
depth: 4,
|
depth: 4,
|
||||||
|
|||||||
@ -111,14 +111,12 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
const potentialServices = await Promise.allSettled(
|
const potentialServices = await Promise.allSettled(
|
||||||
providers.map(provider => provider.getPrometheusService(apiClient)),
|
providers.map(provider => provider.getPrometheusService(apiClient)),
|
||||||
);
|
);
|
||||||
const errors: any[] = [];
|
const errors = [];
|
||||||
|
|
||||||
for (const res of potentialServices) {
|
for (const res of potentialServices) {
|
||||||
switch (res.status) {
|
switch (res.status) {
|
||||||
case "rejected":
|
case "rejected":
|
||||||
if (res.reason) {
|
errors.push(res.reason);
|
||||||
errors.push(String(res.reason));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "fulfilled":
|
case "fulfilled":
|
||||||
@ -128,7 +126,7 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Object.assign(new Error("No Prometheus service found"), { cause: errors });
|
throw new Error("No Prometheus service found", { cause: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveAuthProxyUrl(): Promise<string> {
|
async resolveAuthProxyUrl(): Promise<string> {
|
||||||
|
|||||||
@ -28,14 +28,17 @@ const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[],
|
|||||||
try {
|
try {
|
||||||
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
|
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isRequestError(error)) {
|
if (
|
||||||
if (lastAttempt || (error.statusCode && error.statusCode >= 400 && error.statusCode < 500)) {
|
!isRequestError(error)
|
||||||
throw new Error("Metrics not available", { cause: error });
|
|| lastAttempt
|
||||||
}
|
|| (
|
||||||
} else if (error instanceof Error) {
|
!lastAttempt && (
|
||||||
|
typeof error.statusCode === "number" &&
|
||||||
|
400 <= error.statusCode && error.statusCode < 500
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw new Error("Metrics not available", { cause: error });
|
throw new Error("Metrics not available", { cause: error });
|
||||||
} else {
|
|
||||||
throw new Error("Metrics not available");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
|
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
|
||||||
|
|||||||
@ -107,7 +107,7 @@ export class KubeWatchApi {
|
|||||||
unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController }));
|
unsubscribe.push(store.subscribe({ onLoadFailure, abortController: childController }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof DOMException)) {
|
if (!(error instanceof DOMException)) {
|
||||||
this.log(Object.assign(new Error("Loading stores has failed"), { cause: error }), {
|
this.log(new Error("Loading stores has failed", { cause: error }), {
|
||||||
meta: { store, namespaces },
|
meta: { store, namespaces },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
13
yarn.lock
13
yarn.lock
@ -4118,7 +4118,7 @@ colors@1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||||
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
||||||
|
|
||||||
colors@^1.3.3, colors@^1.4.0:
|
colors@^1.3.3:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
||||||
@ -8462,7 +8462,7 @@ lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17
|
|||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
logform@^2.2.0, logform@^2.3.2, logform@^2.4.0:
|
logform@^2.3.2, logform@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe"
|
resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe"
|
||||||
integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==
|
integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==
|
||||||
@ -12851,15 +12851,6 @@ win-ca@^3.5.0:
|
|||||||
node-forge "^1.2.1"
|
node-forge "^1.2.1"
|
||||||
split "^1.0.1"
|
split "^1.0.1"
|
||||||
|
|
||||||
winston-console-format@^1.0.8:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/winston-console-format/-/winston-console-format-1.0.8.tgz#591adc8e9567c3397a3fa2e29e596d56e48db840"
|
|
||||||
integrity sha512-dq7t/E0D0QRi4XIOwu6HM1+5e//WPqylH88GVjKEhQVrzGFg34MCz+G7pMJcXFBen9C0kBsu5GYgbYsE2LDwKw==
|
|
||||||
dependencies:
|
|
||||||
colors "^1.4.0"
|
|
||||||
logform "^2.2.0"
|
|
||||||
triple-beam "^1.3.0"
|
|
||||||
|
|
||||||
winston-transport-browserconsole@^1.0.5:
|
winston-transport-browserconsole@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/winston-transport-browserconsole/-/winston-transport-browserconsole-1.0.5.tgz#8ef1bc32da5fb0a66604f2b8b6f127ed725108c9"
|
resolved "https://registry.yarnpkg.com/winston-transport-browserconsole/-/winston-transport-browserconsole-1.0.5.tgz#8ef1bc32da5fb0a66604f2b8b6f127ed725108c9"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user