Add winston formatting support for error causes (#6576)

* Add winston formatting support for error causes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix failing to run built version

- Finally make logger fully injectable
- Simplify startMainApplication to only have runMany(Sync) invocations
  to fix time of use bugs related to logger

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove legacy type enforced ipc to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-11-17 14:56:16 +02:00
1 parent a4d0451dec
commit 8dd1d1a8fa
28 files changed
+544 -506

No files matched your search

@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import appPathsInjectable from "./app-paths.injectable";
const directoryForLogsInjectable = getInjectable({
id: "directory-for-logs",
instantiate: (di) => di.inject(appPathsInjectable).logs,
});
export default directoryForLogsInjectable;
@@ -1,131 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { EventEmitter } from "events";
import { onCorrect, onceCorrect } from "../type-enforced-ipc";
describe("type enforced ipc tests", () => {
describe("onCorrect tests", () => {
it("should call the handler if the args are valid", () => {
let called = false;
const source = new EventEmitter();
const listener = () => called = true;
const verifier = (args: unknown[]): args is [] => true;
const channel = "foobar";
onCorrect({ source, listener, verifier, channel });
source.emit(channel);
expect(called).toBe(true);
});
it("should not call the handler if the args are not valid", () => {
let called = false;
const source = new EventEmitter();
const listener = () => called = true;
const verifier = (args: unknown[]): args is [] => false;
const channel = "foobar";
onCorrect({ source, listener, verifier, channel });
source.emit(channel);
expect(called).toBe(false);
});
it("should call the handler twice if the args are valid on two emits", () => {
let called = 0;
const source = new EventEmitter();
const listener = () => called += 1;
const verifier = (args: unknown[]): args is [] => true;
const channel = "foobar";
onCorrect({ source, listener, verifier, channel });
source.emit(channel);
source.emit(channel);
expect(called).toBe(2);
});
it("should call the handler twice if the args are [valid, invalid, valid]", () => {
let called = 0;
const source = new EventEmitter();
const listener = () => called += 1;
const results = [true, false, true];
const verifier = (args: unknown[]): args is [] => results.pop() ?? false;
const channel = "foobar";
onCorrect({ source, listener, verifier, channel });
source.emit(channel);
source.emit(channel);
source.emit(channel);
expect(called).toBe(2);
});
});
describe("onceCorrect tests", () => {
it("should call the handler if the args are valid", () => {
let called = false;
const source = new EventEmitter();
const listener = () => called = true;
const verifier = (args: unknown[]): args is [] => true;
const channel = "foobar";
onceCorrect({ source, listener, verifier, channel });
source.emit(channel);
expect(called).toBe(true);
});
it("should not call the handler if the args are not valid", () => {
let called = false;
const source = new EventEmitter();
const listener = () => called = true;
const verifier = (args: unknown[]): args is [] => false;
const channel = "foobar";
onceCorrect({ source, listener, verifier, channel });
source.emit(channel);
expect(called).toBe(false);
});
it("should call the handler only once even if args are valid multiple times", () => {
let called = 0;
const source = new EventEmitter();
const listener = () => called += 1;
const verifier = (args: unknown[]): args is [] => true;
const channel = "foobar";
onceCorrect({ source, listener, verifier, channel });
source.emit(channel);
source.emit(channel);
expect(called).toBe(1);
});
it("should call the handler on only the first valid set of args", () => {
let called = "";
let verifierCalled = 0;
const source = new EventEmitter();
const listener = (info: any, arg: string) => called = arg;
const verifier = (args: unknown[]): args is [string] => (++verifierCalled) % 3 === 0;
const channel = "foobar";
onceCorrect({ source, listener, verifier, channel });
source.emit(channel, {}, "a");
source.emit(channel, {}, "b");
source.emit(channel, {}, "c");
source.emit(channel, {}, "d");
source.emit(channel, {}, "e");
source.emit(channel, {}, "f");
source.emit(channel, {}, "g");
source.emit(channel, {}, "h");
source.emit(channel, {}, "i");
expect(called).toBe("c");
});
});
});
-1
View File
@@ -5,4 +5,3 @@
export * from "./ipc";
export * from "./invalid-kubeconfig";
export * from "./type-enforced-ipc";
-107
View File
@@ -1,107 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { EventEmitter } from "events";
import { ipcMain } from "electron";
import logger from "../../main/logger";
import type { Disposer } from "../utils";
import { ipcMainHandle } from "./ipc";
export type ListenerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
export type Rest<T> = T extends [any, ...infer R] ? R : [];
/**
* Adds a listener to `source` that waits for the first IPC message with the correct
* argument data is sent.
* @param channel The channel to be listened on
* @param listener The function for the channel to be called if the args of the correct type
* @param verifier The function to be called to verify that the args are the correct type
*/
export function onceCorrect<
IPC extends EventEmitter,
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any,
>({
source,
channel,
listener,
verifier,
}: {
source: IPC;
channel: string;
listener: Listener;
verifier: ListVerifier<Rest<Parameters<Listener>>>;
}): void {
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]): void {
if (verifier(args)) {
source.removeListener(channel, wrappedListener); // remove immediately
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
} else {
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
}
}
source.on(channel, wrappedListener);
}
/**
* Adds a listener to `source` that checks to verify the arguments before calling the handler.
* @param channel The channel to be listened on
* @param listener The function for the channel to be called if the args of the correct type
* @param verifier The function to be called to verify that the args are the correct type
*/
export function onCorrect<
IPC extends EventEmitter,
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any,
>({
source,
channel,
listener,
verifier,
}: {
source: IPC;
channel: string;
listener: Listener;
verifier: ListVerifier<Rest<Parameters<Listener>>>;
}): Disposer {
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
if (verifier(args)) {
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
} else {
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
}
}
source.on(channel, wrappedListener);
return () => source.off(channel, wrappedListener);
}
export function handleCorrect<
Handler extends (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
>({
channel,
handler,
verifier,
}: {
channel: string;
handler: Handler;
verifier: ListVerifier<Rest<Parameters<Handler>>>;
}): Disposer {
function wrappedHandler(event: Electron.IpcMainInvokeEvent, ...args: unknown[]): ReturnType<Handler> {
if (verifier(args)) {
return handler(event, ...args);
}
throw new TypeError(`Invalid args for invoke on channel: ${channel}`);
}
ipcMainHandle(channel, wrappedHandler);
return () => ipcMain.removeHandler(channel);
}
+9 -2
View File
@@ -3,12 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { createLogger, format } from "winston";
import type { Logger } from "./logger";
import logger from "./logger";
import { loggerTransportInjectionToken } from "./logger/transports";
const loggerInjectable = getInjectable({
id: "logger",
instantiate: (): Logger => logger,
instantiate: (di): Logger => createLogger({
format: format.combine(
format.splat(),
format.simple(),
),
transports: di.injectMany(loggerTransportInjectionToken),
}),
});
export default loggerInjectable;
+6 -67
View File
@@ -3,12 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { app, ipcMain } from "electron";
import winston, { format } from "winston";
import type Transport from "winston-transport";
import { consoleFormat } from "winston-console-format";
import { isDebugging, isTestEnv } from "./vars";
import BrowserConsole from "winston-transport-browserconsole";
import { asLegacyGlobalForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import loggerInjectable from "./logger.injectable";
export interface Logger {
info: (message: string, ...args: any) => void;
@@ -18,64 +14,7 @@ export interface Logger {
silly: (message: string, ...args: any) => void;
}
const logLevel = process.env.LOG_LEVEL
? process.env.LOG_LEVEL
: isDebugging
? "debug"
: isTestEnv
? "error"
: "info";
const transports: Transport[] = [];
if (ipcMain) {
transports.push(
new winston.transports.Console({
handleExceptions: false,
level: logLevel,
format: format.combine(
format.colorize({ level: true, message: false }),
format.padLevels(),
format.ms(),
consoleFormat({
showMeta: true,
inspectOptions: {
depth: 4,
colors: true,
maxArrayLength: 10,
breakLength: 120,
compact: Infinity,
},
}),
),
}),
);
if (!isTestEnv) {
transports.push(
new winston.transports.File({
handleExceptions: false,
level: "debug",
filename: "lens.log",
/**
* SAFTEY: the `ipcMain` check above should mean that this is only
* called in the main process
*/
dirname: app.getPath("logs"),
maxsize: 1024 * 1024,
maxFiles: 16,
tailable: true,
}),
);
}
} else {
transports.push(new BrowserConsole());
}
export default winston.createLogger({
format: format.combine(
format.splat(),
format.simple(),
),
transports,
}) as Logger;
/**
* @deprecated use `di.inject(loggerInjectable)` instead
*/
export default asLegacyGlobalForExtensionApi(loggerInjectable);
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type TransportStream from "winston-transport";
export const loggerTransportInjectionToken = getInjectionToken<TransportStream>({
id: "logger-transport",
});