From 545a092805fcc080ecf2a8ad63c71e0c71f56253 Mon Sep 17 00:00:00 2001 From: chh <1474479+chenhunghan@users.noreply.github.com> Date: Fri, 9 Jul 2021 11:43:26 +0300 Subject: [PATCH] Revert "Cleanup sentry integration (#3315)" This reverts commit 77f8ea67bd78da6721114d40e285dc3296087ada. --- src/common/sentry.ts | 141 ++++++++++-------- src/common/utils/index.ts | 2 + src/common/utils/isValidSentryDsn.ts | 39 +++++ src/common/utils/isValidURL.ts | 39 +++++ src/common/vars.ts | 6 +- src/main/index.ts | 11 +- src/renderer/bootstrap.tsx | 8 - .../components/+preferences/preferences.tsx | 4 +- src/renderer/lens-app.tsx | 3 + 9 files changed, 175 insertions(+), 78 deletions(-) create mode 100644 src/common/utils/isValidSentryDsn.ts create mode 100644 src/common/utils/isValidURL.ts diff --git a/src/common/sentry.ts b/src/common/sentry.ts index 36435b86c9..ffe6a2b659 100644 --- a/src/common/sentry.ts +++ b/src/common/sentry.ts @@ -20,73 +20,96 @@ */ import { CaptureConsole, Dedupe, Offline } from "@sentry/integrations"; -import { sentryDsn, isProduction } from "./vars"; +import type { Event as SentryEvent } from "@sentry/types"; +import { sentryDsn, sentryDsnIsValid, isProduction } from "./vars"; import { UserStore } from "./user-store"; import logger from "../main/logger"; -export let sentryIsInitialized = false; +// https://www.electronjs.org/docs/api/process#processtype-readonly +const electronProcessType = ["browser", "renderer", "worker"] as const; -/** - * This function bypasses webpack issues. - * - * See: https://docs.sentry.io/platforms/javascript/guides/electron/#webpack-configuration - */ -async function requireSentry() { - switch (process.type) { - case "browser": - return import("@sentry/electron/dist/main"); - case "renderer": - return import("@sentry/electron/dist/renderer"); - default: - throw new Error(`Unsupported process type ${process.type}`); +export const integrations = [ + new CaptureConsole({ levels: ["error"] }), + new Dedupe(), + new Offline() +]; + +const initialScope = (processType: typeof electronProcessType[number]) => { + return { + tags: { + // "translate" browser to 'main' as Lens developer more familiar with the term 'main' + "process": processType === "browser" ? "main" : "renderer" + } + }; +}; + +export const environment = isProduction ? "production" : "development"; + +const logInitError = (reason: string) => { + logger.warn(`⚠️ [SENTRY-INIT]: ${reason}, Sentry is not initialized.`); +}; + +export const beforeSend = (event: SentryEvent) => { + const allowErrorReporting = UserStore.getInstance().allowErrorReporting; + + if (allowErrorReporting) return event; + + logger.info(`🔒 [SENTRY-BEFORE-SEND-HOOK]: allowErrorReporting: ${allowErrorReporting}. Sentry event is caught but not sent to server.`); + logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ==="); + logger.info(event); + logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ==="); + + // if return null, the event won't be sent + // ref https://github.com/getsentry/sentry-javascript/issues/2039 + return null; +}; + +export const SentryInit = () => { + if (!sentryDsnIsValid) { + logInitError("Sentry DSN is not valid"); + + return; } -} -/** - * Initialize Sentry for the current process so to send errors for debugging. - */ -export async function SentryInit(): Promise { + const processType = process.type as typeof electronProcessType[number]; + + let Sentry; + try { - const Sentry = await requireSentry(); + // if in main process + if (processType === "browser") { + Sentry = require("@sentry/electron/dist/main"); + } - try { - Sentry.init({ - beforeSend: event => { - if (UserStore.getInstance().allowErrorReporting) { - return event; - } - - logger.info(`🔒 [SENTRY-BEFORE-SEND-HOOK]: allowErrorReporting: false. Sentry event is caught but not sent to server.`); - logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ==="); - logger.info(event); - logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ==="); - - // if return null, the event won't be sent - // ref https://github.com/getsentry/sentry-javascript/issues/2039 - return null; - }, - dsn: sentryDsn, - integrations: [ - new CaptureConsole({ levels: ["error"] }), - new Dedupe(), - new Offline() - ], - initialScope: { - tags: { - // "translate" browser to 'main' as Lens developer more familiar with the term 'main' - "process": process.type === "browser" ? "main" : "renderer" - } - }, - environment: isProduction ? "production" : "development", - }); - - sentryIsInitialized = true; - - logger.info(`✔️ [SENTRY-INIT]: Sentry for ${process.type} is initialized.`); - } catch (error) { - logger.warn(`⚠️ [SENTRY-INIT]: Sentry.init() error: ${error?.message ?? error}.`); + // if in renderer process + if (processType === "renderer") { + Sentry = require("@sentry/electron/dist/renderer"); } } catch (error) { - logger.warn(`⚠️ [SENTRY-INIT]: Error loading Sentry module ${error?.message ?? error}.`); + logInitError(`Error loading Sentry module ${error?.message}`); + + return; } -} + + + if (!Sentry) { + logInitError(`Unsupported process type ${processType}`); + + return; + } + + try { + Sentry.init({ + beforeSend, + dsn: sentryDsn, + integrations, + initialScope: initialScope(processType), + environment + }); + logger.info(`✔️ [SENTRY-INIT]: Sentry for ${processType} is initialized.`); + } catch (error) { + logInitError(`Sentry.init() error ${error?.message}`); + + return; + } +}; diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 1644c07f36..5ce6d20f55 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -51,6 +51,8 @@ export * from "./tar"; export * from "./toggle-set"; export * from "./toJS"; export * from "./type-narrowing"; +export * from "./isValidURL"; +export * from "./isValidSentryDsn"; import * as iter from "./iter"; diff --git a/src/common/utils/isValidSentryDsn.ts b/src/common/utils/isValidSentryDsn.ts new file mode 100644 index 0000000000..9b91ae1cac --- /dev/null +++ b/src/common/utils/isValidSentryDsn.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * 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. + */ + + +// Regular expression used to parse a Sentry Dsn. +// from https://github.com/getsentry/sentry-javascript/blob/f624f442fd76bf655de6def9fa4d7631735ebba0/packages/utils/src/dsn.ts#L6 +const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/; + +/** + * check if string is a valid Sentry dsn + * + * @param {(string | undefined | null)} string + * @return {boolean} + */ +export const isValidSentryDsn = (string: string | undefined | null) => { + + const valid = DSN_REGEX.exec(string); + + return valid; + +}; diff --git a/src/common/utils/isValidURL.ts b/src/common/utils/isValidURL.ts new file mode 100644 index 0000000000..cf106f34da --- /dev/null +++ b/src/common/utils/isValidURL.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * 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. + */ + + +/** + * check if string is valid URL + * + * @param {(string | undefined | null)} url + * @return {boolean} + */ +export const isValidURL = (url: string | undefined | null) => { + try { + new URL(url); + + return true; + } catch { + // ignore TypeError and return false + } + + return false; +}; diff --git a/src/common/vars.ts b/src/common/vars.ts index 74dcb0abf3..f049d8b656 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -24,6 +24,8 @@ import path from "path"; import { SemVer } from "semver"; import packageInfo from "../../package.json"; import { defineGlobal } from "./utils/defineGlobal"; +import { isValidURL } from "./utils/isValidURL"; +import { isValidSentryDsn } from "./utils/isValidSentryDsn"; export const isMac = process.platform === "darwin"; export const isWindows = process.platform === "win32"; @@ -69,6 +71,8 @@ export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5 export const supportUrl = "https://docs.k8slens.dev/latest/support/" as string; export const appSemVer = new SemVer(packageInfo.version); -export const docsUrl = "https://docs.k8slens.dev/main/" as string; +export const docsUrl = `https://docs.k8slens.dev/main/` as string; export const sentryDsn = packageInfo.config?.sentryDsn; +export const sentryDsnIsValid = isValidURL(sentryDsn) && isValidSentryDsn(sentryDsn); + diff --git a/src/main/index.ts b/src/main/index.ts index 20e1c8d19c..29b3798d7a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -61,6 +61,8 @@ import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "./extension-filesystem"; import { SentryInit } from "../common/sentry"; +SentryInit(); + const workingDir = path.join(app.getPath("appData"), appName); const cleanup = disposer(); @@ -139,15 +141,8 @@ app.on("ready", async () => { logger.info("💾 Loading stores"); - UserStore.createInstance().startMainReactions(); - - /** - * There is no point setting up sentry before UserStore is initialized as - * `allowErrorReporting` will always be falsy. - */ - await SentryInit(); - ClusterStore.createInstance().provideInitialFromMain(); + UserStore.createInstance().startMainReactions(); HotbarStore.createInstance(); ExtensionsStore.createInstance(); FilesystemProvisionerStore.createInstance(); diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 21dbca9c1a..7768a9e0eb 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -47,7 +47,6 @@ import { WeblinkStore } from "../common/weblink-store"; import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "../main/extension-filesystem"; import { ThemeStore } from "./theme.store"; -import { SentryInit } from "../common/sentry"; configurePackages(); @@ -86,13 +85,6 @@ export async function bootstrap(App: AppComponent) { ExtensionDiscovery.createInstance().init(); UserStore.createInstance(); - - /** - * There is no point setting up sentry before UserStore is initialized as - * `allowErrorReporting` will always be falsy. - */ - await SentryInit(); - await ClusterStore.createInstance().loadInitialOnRenderer(); HotbarStore.createInstance(); ExtensionsStore.createInstance(); diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index 285c9b2c75..3ec4693fe8 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -41,7 +41,7 @@ import { FormSwitch, Switcher } from "../switch"; import { KubeconfigSyncs } from "./kubeconfig-syncs"; import { SettingLayout } from "../layout/setting-layout"; import { Checkbox } from "../checkbox"; -import { sentryIsInitialized } from "../../../common/sentry"; +import { sentryDsnIsValid } from "../../../common/vars"; enum Pages { Application = "application", @@ -259,7 +259,7 @@ export class Preferences extends React.Component {

Telemetry

{telemetryExtensions.map(this.renderExtension)} - {sentryIsInitialized ? ( + {sentryDsnIsValid ? (
diff --git a/src/renderer/lens-app.tsx b/src/renderer/lens-app.tsx index 35151dcee2..cbe0d27f6d 100644 --- a/src/renderer/lens-app.tsx +++ b/src/renderer/lens-app.tsx @@ -36,6 +36,9 @@ import { registerIpcHandlers } from "./ipc"; import { ipcRenderer } from "electron"; import { IpcRendererNavigationEvents } from "./navigation/events"; import { catalogEntityRegistry } from "./api/catalog-entity-registry"; +import { SentryInit } from "../common/sentry"; + +SentryInit(); @observer export class LensApp extends React.Component {