Add sentry integration (#1141)

This commit is contained in:
Kostas Bariotis 2020-03-31 09:37:51 +01:00 committed by GitHub
parent b7f681a068
commit 4ecbbab7da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 190 additions and 112 deletions

41
.github/workflows/sentry-production.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: New Sentry Production Release
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1.0.0
- name: Setup Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install and build
run: |
npm ci
npm run build:app
env:
CI: true
- name: Install Sentry
run: |
curl -sL https://sentry.io/get-cli/ | bash
- name: Create new Sentry release
run: |
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
sentry-cli releases new $SENTRY_RELEASE --project $SENTRY_PROJECT
sentry-cli releases set-commits --auto $SENTRY_RELEASE
sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps --no-rewrite ./build/static/js/ --url-prefix "~/static/js"
sentry-cli releases finalize $SENTRY_RELEASE
sentry-cli releases deploys $SENTRY_RELEASE new -e production
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

41
.github/workflows/sentry-staging.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: New Sentry Staging Release
on:
pull_request:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1.0.0
- name: Setup Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install and build
run: |
npm ci
npm run build:app
env:
CI: true
- name: Install Sentry
run: |
curl -sL https://sentry.io/get-cli/ | bash
- name: Create new Sentry release
run: |
export SENTRY_RELEASE=$(sentry-cli releases propose-version)
sentry-cli releases new $SENTRY_RELEASE --project $SENTRY_PROJECT
sentry-cli releases set-commits --auto $SENTRY_RELEASE
sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps --no-rewrite ./build/static/js/ --url-prefix "~/static/js"
sentry-cli releases finalize $SENTRY_RELEASE
sentry-cli releases deploys $SENTRY_RELEASE new -e staging
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

104
package-lock.json generated
View File

@ -1341,6 +1341,63 @@
"any-observable": "^0.3.0" "any-observable": "^0.3.0"
} }
}, },
"@sentry/browser": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.4.tgz",
"integrity": "sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw==",
"requires": {
"@sentry/core": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/core": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.4.tgz",
"integrity": "sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ==",
"requires": {
"@sentry/hub": "5.15.4",
"@sentry/minimal": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.4.tgz",
"integrity": "sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg==",
"requires": {
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.4.tgz",
"integrity": "sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw==",
"requires": {
"@sentry/hub": "5.15.4",
"@sentry/types": "5.15.4",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.4.tgz",
"integrity": "sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg=="
},
"@sentry/utils": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.4.tgz",
"integrity": "sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ==",
"requires": {
"@sentry/types": "5.15.4",
"tslib": "^1.9.3"
}
},
"@svgr/babel-plugin-add-jsx-attribute": { "@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@ -5219,14 +5276,6 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"error-stack-parser": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
"requires": {
"stackframe": "^1.1.1"
}
},
"es-abstract": { "es-abstract": {
"version": "1.17.4", "version": "1.17.4",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
@ -14357,50 +14406,11 @@
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
"integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
}, },
"stack-generator": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz",
"integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==",
"requires": {
"stackframe": "^1.1.1"
}
},
"stack-utils": { "stack-utils": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
"integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
}, },
"stackframe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz",
"integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ=="
},
"stacktrace-gps": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz",
"integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==",
"requires": {
"source-map": "0.5.6",
"stackframe": "^1.1.1"
},
"dependencies": {
"source-map": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
}
}
},
"stacktrace-js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz",
"integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==",
"requires": {
"error-stack-parser": "^2.0.6",
"stack-generator": "^2.0.5",
"stacktrace-gps": "^3.0.4"
}
},
"static-extend": { "static-extend": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",

View File

@ -19,6 +19,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@sentry/browser": "5.15.4",
"browser-nativefs": "0.4.0", "browser-nativefs": "0.4.0",
"i18next-browser-languagedetector": "4.0.2", "i18next-browser-languagedetector": "4.0.2",
"nanoid": "2.1.11", "nanoid": "2.1.11",
@ -26,8 +27,7 @@
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"roughjs": "4.0.4", "roughjs": "4.0.4",
"socket.io-client": "2.3.0", "socket.io-client": "2.3.0"
"stacktrace-js": "2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "5.3.0", "@testing-library/jest-dom": "5.3.0",
@ -100,7 +100,7 @@
"scripts": { "scripts": {
"build": "npm run build:app && npm run build:zip", "build": "npm run build:app && npm run build:zip",
"build-node": "node ./scripts/build-node.js", "build-node": "node ./scripts/build-node.js",
"build:app": "react-scripts build", "build:app": "REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build",
"build:zip": "node ./scripts/build-version.js", "build:zip": "node ./scripts/build-version.js",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"fix": "npm run fix:other && npm run fix:code", "fix": "npm run fix:other && npm run fix:code",

View File

@ -1,13 +1,11 @@
export default ` export default (sentryErrorId) => `
### Stack trace
\`\`\`
// paste stack trace here
\`\`\`
### Scene content ### Scene content
\`\`\` \`\`\`
// paste scene content here (if it doesn't contain sensitive data) Paste scene content here
\`\`\` \`\`\`
### Sentry Error ID
${sentryErrorId}
`; `;

View File

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import * as Sentry from "@sentry/browser";
import { resetCursor } from "../utils"; import { resetCursor } from "../utils";
import { t } from "../i18n"; import { t } from "../i18n";
interface TopErrorBoundaryState { interface TopErrorBoundaryState {
unresolvedError: Error[] | null;
hasError: boolean; hasError: boolean;
stack: string; sentryEventId: string;
localStorage: string; localStorage: string;
} }
@ -14,9 +14,8 @@ export class TopErrorBoundary extends React.Component<
TopErrorBoundaryState TopErrorBoundaryState
> { > {
state: TopErrorBoundaryState = { state: TopErrorBoundaryState = {
unresolvedError: null,
hasError: false, hasError: false,
stack: "", sentryEventId: "",
localStorage: "", localStorage: "",
}; };
@ -24,7 +23,7 @@ export class TopErrorBoundary extends React.Component<
return this.state.hasError ? this.errorSplash() : this.props.children; return this.state.hasError ? this.errorSplash() : this.props.children;
} }
componentDidCatch(error: Error) { componentDidCatch(error: Error, errorInfo: any) {
resetCursor(); resetCursor();
const _localStorage: any = {}; const _localStorage: any = {};
for (const [key, value] of Object.entries({ ...localStorage })) { for (const [key, value] of Object.entries({ ...localStorage })) {
@ -34,39 +33,17 @@ export class TopErrorBoundary extends React.Component<
_localStorage[key] = value; _localStorage[key] = value;
} }
} }
this.setState((state) => ({
hasError: true,
unresolvedError: state.unresolvedError
? state.unresolvedError.concat(error)
: [error],
localStorage: JSON.stringify(_localStorage),
}));
}
async componentDidUpdate() { Sentry.withScope((scope) => {
if (this.state.unresolvedError !== null) { scope.setExtras(errorInfo);
let stack = ""; const eventId = Sentry.captureException(error);
for (const error of this.state.unresolvedError) {
if (stack) {
stack += `\n\n================\n\n`;
}
stack += `${error.message}:\n\n`;
try {
const StackTrace = await import("stacktrace-js");
stack += (await StackTrace.fromError(error)).join("\n");
} catch (error) {
console.error(error);
stack += error.stack || "";
}
}
this.setState((state) => ({ this.setState((state) => ({
unresolvedError: null, hasError: true,
stack: `${ sentryEventId: eventId,
state.stack ? `${state.stack}\n\n================\n\n${stack}` : stack localStorage: JSON.stringify(_localStorage),
}`,
})); }));
} });
} }
private selectTextArea(event: React.MouseEvent<HTMLTextAreaElement>) { private selectTextArea(event: React.MouseEvent<HTMLTextAreaElement>) {
@ -79,10 +56,8 @@ export class TopErrorBoundary extends React.Component<
private async createGithubIssue() { private async createGithubIssue() {
let body = ""; let body = "";
try { try {
const templateStr = (await import("../bug-issue-template")).default; const templateStrFn = (await import("../bug-issue-template")).default;
if (typeof templateStr === "string") { body = encodeURIComponent(templateStrFn(this.state.sentryEventId));
body = encodeURIComponent(templateStr);
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -124,26 +99,20 @@ export class TopErrorBoundary extends React.Component<
</div> </div>
</div> </div>
<div> <div>
<div className="ErrorSplash-paragraph">
{t("errorSplash.trackedToSentry_pre")}
{this.state.sentryEventId}
{t("errorSplash.trackedToSentry_post")}
</div>
<div className="ErrorSplash-paragraph"> <div className="ErrorSplash-paragraph">
{t("errorSplash.openIssueMessage_pre")} {t("errorSplash.openIssueMessage_pre")}
<button onClick={this.createGithubIssue}> <button onClick={() => this.createGithubIssue()}>
{t("errorSplash.openIssueMessage_button")} {t("errorSplash.openIssueMessage_button")}
</button> </button>
{t("errorSplash.openIssueMessage_post")} {t("errorSplash.openIssueMessage_post")}
</div> </div>
<div className="ErrorSplash-paragraph"> <div className="ErrorSplash-paragraph">
<div className="ErrorSplash-details"> <div className="ErrorSplash-details">
<label>{t("errorSplash.errorStack")}</label>
<textarea
rows={10}
onPointerDown={this.selectTextArea}
readOnly={true}
value={
this.state.unresolvedError
? t("errorSplash.errorStack_loading")
: this.state.stack
}
/>
<label>{t("errorSplash.sceneContent")}</label> <label>{t("errorSplash.sceneContent")}</label>
<textarea <textarea
rows={5} rows={5}

View File

@ -1,10 +1,29 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import * as Sentry from "@sentry/browser";
import { TopErrorBoundary } from "./components/TopErrorBoundary"; import { TopErrorBoundary } from "./components/TopErrorBoundary";
import { IsMobileProvider } from "./is-mobile"; import { IsMobileProvider } from "./is-mobile";
import { App } from "./components/App"; import { App } from "./components/App";
import "./styles.scss"; import "./styles.scss";
const SentyEnvHostnameMap: { [key: string]: string } = {
"excalidraw.com": "production",
"now.sh": "staging",
};
const onlineEnv = Object.keys(SentyEnvHostnameMap).find(
(item) => window.location.hostname.indexOf(item) >= 0,
);
Sentry.init({
// Disable Sentry locally to avoid noise
dsn: onlineEnv
? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260"
: undefined,
environment: onlineEnv ? SentyEnvHostnameMap[onlineEnv] : undefined,
release: process.env.REACT_APP_GIT_SHA,
});
// Block pinch-zooming on iOS outside of the content area // Block pinch-zooming on iOS outside of the content area
document.addEventListener( document.addEventListener(
"touchmove", "touchmove",

View File

@ -105,11 +105,11 @@
"clearCanvasMessage": "If reloading doesn't work, try ", "clearCanvasMessage": "If reloading doesn't work, try ",
"clearCanvasMessage_button": "clearing the canvas.", "clearCanvasMessage_button": "clearing the canvas.",
"clearCanvasCaveat": " This will result in loss of work ", "clearCanvasCaveat": " This will result in loss of work ",
"openIssueMessage_pre": "Before doing so, we'd appreciate if you opened an issue on our ", "trackedToSentry_pre": "The error with identifier ",
"trackedToSentry_post": " was tracked on our system.",
"openIssueMessage_pre": "We were very cautious not to include your scene information on the error. If your scene is not private, please consider following up on our ",
"openIssueMessage_button": "bug tracker.", "openIssueMessage_button": "bug tracker.",
"openIssueMessage_post": " Please include the following error stack trace (and if it's not private, also the scene content):", "openIssueMessage_post": " Please include information below by copying and pasting into the GitHub issue.",
"errorStack": "Error stack trace:",
"errorStack_loading": "Loading data. please wait...",
"sceneContent": "Scene content:" "sceneContent": "Scene content:"
}, },
"roomDialog": { "roomDialog": {