From ac2d0e46ff9db63bbebb58b47a9a6455fc5ced84 Mon Sep 17 00:00:00 2001 From: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:50:22 +0000 Subject: [PATCH] Renderer file logging transport (#6795) * Renderer file logging transport Add file logging to renderer, writing separate log files for renderer main frame and each cluster frame. Related to lensapp/support-lens-extension#118 Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Switch renderer file log level to info There is too much noise on debug level from api responses etc Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Unmount on pagehide instead of beforeunload It seems cluster onbeforeunload is not triggered when iframe is removed from dom by the parent (when disconnecting a cluster). While on root/main frame the beforeunload event does work, it seems to be adviced to use pagehide instead. Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Close log files on unmount Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Improve file handle closing in different situations This should cover reloading main and cluster frames and closing cluster frame throught disconnecting cluster. No file handles should be left open now. Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Fix renderer log rotation Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Switch back to beforeunload in root frame Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> * Remove capturing phase event usage Does not seem to be needed for log files to be successfully closed and caused integration tests to fail. Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> --------- Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> --- packages/core/src/common/logger.injectable.ts | 11 +---- .../src/common/winston-logger.injectable.ts | 20 +++++++++ packages/core/src/renderer/bootstrap.tsx | 4 +- .../init-cluster-frame.injectable.ts | 2 + .../init-cluster-frame/init-cluster-frame.ts | 12 +++-- .../root-frame/init-root-frame.injectable.ts | 4 +- .../close-renderer-log-file.injectable.ts | 22 ++++++++++ .../logger/file-transport.injectable.ts | 44 +++++++++++++++++++ 8 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 packages/core/src/common/winston-logger.injectable.ts create mode 100644 packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts create mode 100644 packages/core/src/renderer/logger/file-transport.injectable.ts diff --git a/packages/core/src/common/logger.injectable.ts b/packages/core/src/common/logger.injectable.ts index 8e9dd2a6a734..82ff682c4673 100644 --- a/packages/core/src/common/logger.injectable.ts +++ b/packages/core/src/common/logger.injectable.ts @@ -3,20 +3,13 @@ * 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 { loggerTransportInjectionToken } from "./logger/transports"; +import winstonLoggerInjectable from "./winston-logger.injectable"; const loggerInjectable = getInjectable({ id: "logger", instantiate: (di): Logger => { - const baseLogger = createLogger({ - format: format.combine( - format.splat(), - format.simple(), - ), - transports: di.injectMany(loggerTransportInjectionToken), - }); + const baseLogger = di.inject(winstonLoggerInjectable); return { debug: (message, ...data) => baseLogger.debug(message, ...data), diff --git a/packages/core/src/common/winston-logger.injectable.ts b/packages/core/src/common/winston-logger.injectable.ts new file mode 100644 index 000000000000..ec3854d8b96b --- /dev/null +++ b/packages/core/src/common/winston-logger.injectable.ts @@ -0,0 +1,20 @@ +/** + * 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 { createLogger, format } from "winston"; +import { loggerTransportInjectionToken } from "./logger/transports"; + +const winstonLoggerInjectable = getInjectable({ + id: "winston-logger", + instantiate: (di) => createLogger({ + format: format.combine( + format.splat(), + format.simple(), + ), + transports: di.injectMany(loggerTransportInjectionToken), + }), +}); + +export default winstonLoggerInjectable; diff --git a/packages/core/src/renderer/bootstrap.tsx b/packages/core/src/renderer/bootstrap.tsx index f509847c7478..f74c74986f40 100644 --- a/packages/core/src/renderer/bootstrap.tsx +++ b/packages/core/src/renderer/bootstrap.tsx @@ -54,7 +54,9 @@ export async function bootstrap(di: DiContainer) { } try { - await initializeApp(() => unmountComponentAtNode(rootElem)); + await initializeApp(() => { + unmountComponentAtNode(rootElem); + }); } catch (error) { console.error(`[BOOTSTRAP]: view initialization error: ${error}`, { origin: location.href, diff --git a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index c640264ee395..a9a923f8600f 100644 --- a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -12,6 +12,7 @@ import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event. import loadExtensionsInjectable from "../../load-extensions.injectable"; import loggerInjectable from "../../../../common/logger.injectable"; import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable"; +import closeRendererLogFileInjectable from "../../../logger/close-renderer-log-file.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -29,6 +30,7 @@ const initClusterFrameInjectable = getInjectable({ emitAppEvent: di.inject(emitAppEventInjectable), logger: di.inject(loggerInjectable), showErrorNotification: di.inject(showErrorNotificationInjectable), + closeFileLogging: di.inject(closeRendererLogFileInjectable), }); }, }); diff --git a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts index 109ae0f0bc6a..31f4bfe96e03 100644 --- a/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts +++ b/packages/core/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts @@ -2,6 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { once } from "lodash"; import type { Cluster } from "../../../../common/cluster/cluster"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry"; import type { ShowNotification } from "../../../components/notifications"; @@ -18,6 +19,7 @@ interface Dependencies { emitAppEvent: EmitAppEvent; logger: Logger; showErrorNotification: ShowNotification; + closeFileLogging: () => void; } const logPrefix = "[CLUSTER-FRAME]:"; @@ -30,6 +32,7 @@ export const initClusterFrame = ({ emitAppEvent, logger, showErrorNotification, + closeFileLogging, }: Dependencies) => async (unmountRoot: () => void) => { // TODO: Make catalogEntityRegistry already initialized when passed as dependency @@ -73,11 +76,14 @@ export const initClusterFrame = ({ }); }); - window.onbeforeunload = () => { + const onCloseFrame = once(() => { logger.info( `${logPrefix} Unload dashboard, clusterId=${(hostedCluster.id)}, frameId=${frameRoutingId}`, ); - + closeFileLogging(); unmountRoot(); - }; + }); + + window.addEventListener("beforeunload", onCloseFrame); + window.addEventListener("pagehide", onCloseFrame); }; diff --git a/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts b/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts index 8d2c3a43be2c..2f698c5c89ba 100644 --- a/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts +++ b/packages/core/src/renderer/frames/root-frame/init-root-frame.injectable.ts @@ -13,6 +13,7 @@ import loggerInjectable from "../../../common/logger.injectable"; import { delay } from "../../../common/utils"; import { broadcastMessage } from "../../../common/ipc"; import { bundledExtensionsLoaded } from "../../../common/ipc/extension-handling"; +import closeRendererLogFileInjectable from "../../logger/close-renderer-log-file.injectable"; const initRootFrameInjectable = getInjectable({ id: "init-root-frame", @@ -24,6 +25,7 @@ const initRootFrameInjectable = getInjectable({ const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable); const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); const logger = di.inject(loggerInjectable); + const closeRendererLogFile = di.inject(closeRendererLogFileInjectable); return async (unmountRoot: () => void) => { catalogEntityRegistry.init(); @@ -59,7 +61,7 @@ const initRootFrameInjectable = getInjectable({ window.addEventListener("beforeunload", () => { logger.info("[ROOT-FRAME]: Unload app"); - + closeRendererLogFile(); unmountRoot(); }); }; diff --git a/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts b/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts new file mode 100644 index 000000000000..8480589c3935 --- /dev/null +++ b/packages/core/src/renderer/logger/close-renderer-log-file.injectable.ts @@ -0,0 +1,22 @@ +/** + * 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 winstonLoggerInjectable from "../../common/winston-logger.injectable"; +import rendererFileLoggerTransportInjectable from "./file-transport.injectable"; + +const closeRendererLogFileInjectable = getInjectable({ + id: "close-renderer-log-file", + instantiate: (di) => { + const winstonLogger = di.inject(winstonLoggerInjectable); + const fileLoggingTransport = di.inject(rendererFileLoggerTransportInjectable); + + return () => { + fileLoggingTransport.close?.(); + winstonLogger.remove(fileLoggingTransport); + }; + }, +}); + +export default closeRendererLogFileInjectable; diff --git a/packages/core/src/renderer/logger/file-transport.injectable.ts b/packages/core/src/renderer/logger/file-transport.injectable.ts new file mode 100644 index 000000000000..0cd8607e83a6 --- /dev/null +++ b/packages/core/src/renderer/logger/file-transport.injectable.ts @@ -0,0 +1,44 @@ +/** + * 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 { transports } from "winston"; +import directoryForLogsInjectable from "../../common/app-paths/directory-for-logs.injectable"; +import { loggerTransportInjectionToken } from "../../common/logger/transports"; +import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; +import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable"; +import { getClusterIdFromHost } from "../utils"; + +const rendererFileLoggerTransportInjectable = getInjectable({ + id: "renderer-file-logger-transport", + instantiate: (di) => { + let frameId: string; + + const currentlyInClusterFrame = di.inject( + currentlyInClusterFrameInjectable, + ); + + if (currentlyInClusterFrame) { + const { host } = di.inject(windowLocationInjectable); + const clusterId = getClusterIdFromHost(host); + + frameId = clusterId ? `cluster-${clusterId}` : "cluster"; + } else { + frameId = "main"; + } + + return new transports.File({ + handleExceptions: false, + level: "info", + filename: `lens-renderer-${frameId}.log`, + dirname: di.inject(directoryForLogsInjectable), + maxsize: 1024 * 1024, + maxFiles: 2, + tailable: true, + }); + }, + injectionToken: loggerTransportInjectionToken, +}); + +export default rendererFileLoggerTransportInjectable;