Przeglądaj źródła

Implemented more granular error boundary

jnfrati 4 lat temu
rodzic
commit
606526e204

+ 47 - 47
dashboard/package-lock.json

@@ -2656,13 +2656,13 @@
       "dev": true
     },
     "@sentry/browser": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.12.0.tgz",
-      "integrity": "sha512-wsJi1NLOmfwtPNYxEC50dpDcVY7sdYckzwfqz1/zHrede1mtxpqSw+7iP4bHADOJXuF+ObYYTHND0v38GSXznQ==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz",
+      "integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==",
       "requires": {
-        "@sentry/core": "6.12.0",
-        "@sentry/types": "6.12.0",
-        "@sentry/utils": "6.12.0",
+        "@sentry/core": "6.13.2",
+        "@sentry/types": "6.13.2",
+        "@sentry/utils": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {
@@ -2674,14 +2674,14 @@
       }
     },
     "@sentry/core": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.12.0.tgz",
-      "integrity": "sha512-mU/zdjlzFHzdXDZCPZm8OeCw7c9xsbL49Mq0TrY0KJjLt4CJBkiq5SDTGfRsenBLgTedYhe5Z/J8Z+xVVq+MfQ==",
-      "requires": {
-        "@sentry/hub": "6.12.0",
-        "@sentry/minimal": "6.12.0",
-        "@sentry/types": "6.12.0",
-        "@sentry/utils": "6.12.0",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz",
+      "integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==",
+      "requires": {
+        "@sentry/hub": "6.13.2",
+        "@sentry/minimal": "6.13.2",
+        "@sentry/types": "6.13.2",
+        "@sentry/utils": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {
@@ -2693,12 +2693,12 @@
       }
     },
     "@sentry/hub": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.12.0.tgz",
-      "integrity": "sha512-yR/UQVU+ukr42bSYpeqvb989SowIXlKBanU0cqLFDmv5LPCnaQB8PGeXwJAwWhQgx44PARhmB82S6Xor8gYNxg==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz",
+      "integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==",
       "requires": {
-        "@sentry/types": "6.12.0",
-        "@sentry/utils": "6.12.0",
+        "@sentry/types": "6.13.2",
+        "@sentry/utils": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {
@@ -2710,12 +2710,12 @@
       }
     },
     "@sentry/minimal": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.12.0.tgz",
-      "integrity": "sha512-r3C54Q1KN+xIqUvcgX9DlcoWE7ezWvFk2pSu1Ojx9De81hVqR9u5T3sdSAP2Xma+um0zr6coOtDJG4WtYlOtsw==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz",
+      "integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==",
       "requires": {
-        "@sentry/hub": "6.12.0",
-        "@sentry/types": "6.12.0",
+        "@sentry/hub": "6.13.2",
+        "@sentry/types": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {
@@ -2727,14 +2727,14 @@
       }
     },
     "@sentry/react": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.12.0.tgz",
-      "integrity": "sha512-E8Nw9PPzP/EyMy64ksr9xcyYYlBmUA5ROnkPQp7o5wF0xf5/J+nMS1tQdyPnLQe2KUgHlN4kVs2HHft1m7mSYQ==",
-      "requires": {
-        "@sentry/browser": "6.12.0",
-        "@sentry/minimal": "6.12.0",
-        "@sentry/types": "6.12.0",
-        "@sentry/utils": "6.12.0",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.13.2.tgz",
+      "integrity": "sha512-aLkWyn697LTcmK1PPnUg5UJcyBUPoI68motqgBY53SIYDAwOeYNUQt2aanDuOTY5aE2PdnJwU48klA8vuYkoRQ==",
+      "requires": {
+        "@sentry/browser": "6.13.2",
+        "@sentry/minimal": "6.13.2",
+        "@sentry/types": "6.13.2",
+        "@sentry/utils": "6.13.2",
         "hoist-non-react-statics": "^3.3.2",
         "tslib": "^1.9.3"
       },
@@ -2747,14 +2747,14 @@
       }
     },
     "@sentry/tracing": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.12.0.tgz",
-      "integrity": "sha512-u10QHNknPBzbWSUUNMkvuH53sQd5NaBo6YdNPj4p5b7sE7445Sh0PwBpRbY3ZiUUiwyxV59fx9UQ4yVnPGxZQA==",
-      "requires": {
-        "@sentry/hub": "6.12.0",
-        "@sentry/minimal": "6.12.0",
-        "@sentry/types": "6.12.0",
-        "@sentry/utils": "6.12.0",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.13.2.tgz",
+      "integrity": "sha512-bHJz+C/nd6biWTNcYAu91JeRilsvVgaye4POkdzWSmD0XoLWHVMrpCQobGpXe7onkp2noU3YQjhqgtBqPHtnpw==",
+      "requires": {
+        "@sentry/hub": "6.13.2",
+        "@sentry/minimal": "6.13.2",
+        "@sentry/types": "6.13.2",
+        "@sentry/utils": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {
@@ -2766,16 +2766,16 @@
       }
     },
     "@sentry/types": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.12.0.tgz",
-      "integrity": "sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA=="
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz",
+      "integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg=="
     },
     "@sentry/utils": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.12.0.tgz",
-      "integrity": "sha512-oRHQ7TH5TSsJqoP9Gqq25Jvn9LKexXfAh/OoKwjMhYCGKGhqpDNUIZVgl9DWsGw5A5N5xnQyLOxDfyRV5RshdA==",
+      "version": "6.13.2",
+      "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz",
+      "integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==",
       "requires": {
-        "@sentry/types": "6.12.0",
+        "@sentry/types": "6.13.2",
         "tslib": "^1.9.3"
       },
       "dependencies": {

+ 2 - 2
dashboard/package.json

@@ -4,8 +4,8 @@
   "private": true,
   "dependencies": {
     "@material-ui/core": "^4.11.3",
-    "@sentry/react": "^6.12.0",
-    "@sentry/tracing": "^6.12.0",
+    "@sentry/react": "^6.13.2",
+    "@sentry/tracing": "^6.13.2",
     "@visx/axis": "^1.6.1",
     "@visx/curve": "^1.0.0",
     "@visx/event": "^1.3.0",

+ 2 - 2
dashboard/src/components/UnexpectedErrorPage.tsx

@@ -1,7 +1,7 @@
 import React from "react";
 import styled from "styled-components";
 
-const UnexpectedErrorPage = ({ error, resetError }: any) => (
+const UnexpectedErrorPage = ({ error, resetErrorBoundary }: any) => (
   <>
     <StyledPageNotFound>
       <Mega>
@@ -9,7 +9,7 @@ const UnexpectedErrorPage = ({ error, resetError }: any) => (
         <Inside>Unknown Error</Inside>
       </Mega>
       <Flex>
-        <BackButton width="140px" onClick={() => resetError(error)}>
+        <BackButton width="140px" onClick={() => resetErrorBoundary(error)}>
           <i className="material-icons">arrow_back</i>
           Reload page
         </BackButton>

+ 4 - 1
dashboard/src/main/MainWrapper.tsx

@@ -4,6 +4,7 @@ import { ContextProvider } from "../shared/Context";
 import Main from "./Main";
 import { RouteComponentProps, withRouter } from "react-router";
 import AuthProvider from "shared/auth/AuthContext";
+import MainWrapperErrorBoundary from "./MainWrapperErrorBoundary";
 
 type PropsType = RouteComponentProps & {};
 
@@ -15,7 +16,9 @@ class MainWrapper extends Component<PropsType, StateType> {
     return (
       <ContextProvider history={history} location={location}>
         <AuthProvider>
-          <Main />
+          <MainWrapperErrorBoundary>
+            <Main />
+          </MainWrapperErrorBoundary>
         </AuthProvider>
       </ContextProvider>
     );

+ 33 - 0
dashboard/src/main/MainWrapperErrorBoundary.tsx

@@ -0,0 +1,33 @@
+import React, { useContext } from "react";
+import { Context } from "shared/Context";
+import PorterErrorBoundary from "shared/PorterErrorBoundary";
+
+const MainWrapperErrorBoundary: React.FC = ({ children }) => {
+  const location = "MainWrapperErrorBoundary";
+  const {
+    capabilities,
+    currentCluster,
+    currentProject,
+    devOpsMode,
+    projects,
+  } = useContext(Context);
+
+  return (
+    <PorterErrorBoundary
+      errorBoundaryLocation={location}
+      context={{
+        "Global context state": {
+          capabilities,
+          currentProject,
+          currentCluster,
+          devOpsMode,
+          projects: JSON.stringify(projects),
+        },
+      }}
+    >
+      {children}
+    </PorterErrorBoundary>
+  );
+};
+
+export default MainWrapperErrorBoundary;

+ 41 - 7
dashboard/src/shared/PorterErrorBoundary.tsx

@@ -2,37 +2,71 @@ import UnexpectedErrorPage from "components/UnexpectedErrorPage";
 import React from "react";
 import { ErrorBoundary } from "react-error-boundary";
 import * as Sentry from "@sentry/react";
-import StackTrace from "stacktrace-js";
+import StackTrace, { StackFrame } from "stacktrace-js";
+import { Context, Primitive } from "@sentry/types";
 
 export type PorterErrorBoundaryProps<OnResetProps = {}> = {
   // Component or useful name to describe where the error boundary was setted
   errorBoundaryLocation: string;
   // Used in case the boundary shouldn't refresh but instead do other action
   onReset?: (props: OnResetProps) => unknown;
+  // Add more tags to sentry errors
+  tags?: {
+    [key: string]: Primitive;
+  };
+  // Add more context for sentry errors
+  context?: {
+    [key: string]: Context;
+  };
 };
 
+const stackFramesToString = (stackFrames: StackFrame[]) => {
+  return stackFrames
+    .map(function (sf) {
+      return sf.toString();
+    })
+    .join("\n");
+};
 const PorterErrorBoundary: React.FC<PorterErrorBoundaryProps> = ({
   errorBoundaryLocation,
   onReset,
   children,
+  tags,
+  context,
 }) => {
-  const handleError = (err: Error, info: { componentStack: string }) => {
-    StackTrace.fromError(err).then((error) => {
+  const handleError = (err: Error) => {
+    StackTrace.fromError(err).then((stackframes) => {
+      const stackFramesStringify = stackFramesToString(stackframes);
+      // Preserve the old stack just in case
+      const originalStack = err.stack;
+      // Update the error stack with the StackTrace stack (this helps for minified environments)
+      err.stack = stackFramesStringify;
+
       if (process.env.ENABLE_SENTRY) {
-        Sentry.captureException(error, (scope) => {
+        Sentry.captureException(err, (scope) => {
           scope.setTags({
             error_boundary_location: errorBoundaryLocation,
             error_message: err?.message,
-            component_stack: info?.componentStack,
+            ...(tags || {}),
           });
+          scope.setContext("Original stack", {
+            originalStack,
+          });
+
+          if (typeof context === "object") {
+            Object.entries(context).forEach(([contextName, contextContent]) => {
+              scope.setContext(contextName, contextContent);
+            });
+          }
+
           return scope;
         });
       }
 
       window?.analytics?.track("React Error", {
         location: errorBoundaryLocation,
-        error: error.toString(),
-        componentStack: info?.componentStack,
+        error: stackFramesStringify,
+        componentStack: err.stack,
         url: window.location.toString(),
       });
     });

+ 3 - 2
dashboard/src/shared/sentry/setup.ts

@@ -2,6 +2,7 @@ import * as Sentry from "@sentry/react";
 import { Integrations } from "@sentry/tracing";
 
 const SENTRY_DSN = process.env.SENTRY_DSN;
+const SENTRY_ENV = process.env.SENTRY_ENV || "development";
 
 export const SetupSentry = () => {
   if (!SENTRY_DSN) {
@@ -10,8 +11,8 @@ export const SetupSentry = () => {
   Sentry.init({
     dsn: SENTRY_DSN,
     integrations: [new Integrations.BrowserTracing()],
-
+    environment: SENTRY_ENV,
     // Check out https://docs.sentry.io/platforms/javascript/guides/react/configuration/sampling/ for a more refined sample rate
-    tracesSampleRate: 0.25,
+    tracesSampleRate: 1,
   });
 };