-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Send Crash Reports to Sentry #4571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
fdc2486
Initial sentry implementation
penalosa 5cf6267
Prompt every time
penalosa cd4efec
Add tests
penalosa 1c2bd93
Create pink-bags-push.md
penalosa eb58f0f
Clear event queue
penalosa 899ebc0
Make event queue non-const
penalosa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "wrangler": minor | ||
| --- | ||
|
|
||
| feat: When Wrangler crashes, send an error report to Sentry to aid in debugging. | ||
|
|
||
| When Wrangler's top-level exception handler catches an error thrown from Wrangler's application, it will offer to report the error to Sentry. This requires opt-in from the user every time. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ jobs: | |
| NODE_ENV: "production" | ||
| ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} | ||
| ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} | ||
| SENTRY_DSN: "https://[email protected]/583" | ||
| CI_OS: ${{ runner.os }} | ||
|
|
||
| - name: Pack miniflare | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ jobs: | |
| # this is the "test/staging" key for sparrow analytics | ||
| SPARROW_SOURCE_KEY: "5adf183f94b3436ba78d67f506965998" | ||
| ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} | ||
| SENTRY_DSN: "https://[email protected]/583" | ||
| ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} | ||
| working-directory: packages/wrangler | ||
|
|
||
|
|
@@ -111,6 +112,7 @@ jobs: | |
| NODE_ENV: "production" | ||
| ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} | ||
| ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} | ||
| SENTRY_DSN: "https://[email protected]/583" | ||
| CI_OS: ${{ runner.os }} | ||
|
|
||
| - name: Build & Publish Prerelease Registry | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,8 @@ jobs: | |
| NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} | ||
| ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} | ||
| ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} | ||
| SENTRY_DSN: "https://[email protected]/583" | ||
|
|
||
| NODE_ENV: "production" | ||
| # This is the "production" key for sparrow analytics. | ||
| # Include this here because this step will rebuild Wrangler and needs to have this available | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import { rest } from "msw"; | ||
|
|
||
| import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; | ||
| import { mockConsoleMethods } from "./helpers/mock-console"; | ||
| import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; | ||
| import { useMockIsTTY } from "./helpers/mock-istty"; | ||
| import { msw } from "./helpers/msw"; | ||
| import { runInTempDir } from "./helpers/run-in-tmp"; | ||
| import { runWrangler } from "./helpers/run-wrangler"; | ||
|
|
||
| declare const global: { SENTRY_DSN: string | undefined }; | ||
|
|
||
| describe("sentry", () => { | ||
| const ORIGINAL_SENTRY_DSN = global.SENTRY_DSN; | ||
| const std = mockConsoleMethods(); | ||
| runInTempDir(); | ||
| mockAccountId(); | ||
| mockApiToken(); | ||
| const { setIsTTY } = useMockIsTTY(); | ||
|
|
||
| let sentryRequests: { count: number } | undefined; | ||
|
|
||
| beforeEach(() => { | ||
| global.SENTRY_DSN = | ||
| "https://[email protected]/24601"; | ||
|
|
||
| sentryRequests = mockSentryEndpoint(); | ||
| }); | ||
| afterEach(() => { | ||
| global.SENTRY_DSN = ORIGINAL_SENTRY_DSN; | ||
| clearDialogs(); | ||
| msw.resetHandlers(); | ||
| }); | ||
| describe("non interactive", () => { | ||
| it("should not hit sentry in normal usage", async () => { | ||
| await runWrangler("version"); | ||
| expect(sentryRequests?.count).toEqual(0); | ||
| }); | ||
|
|
||
| it("should not hit sentry after error", async () => { | ||
| await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( | ||
| `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` | ||
| ); | ||
| expect(std.out).toMatchInlineSnapshot(` | ||
| " | ||
| [32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose[0m | ||
| ? Would you like to report this error to Cloudflare? | ||
| 🤖 Using fallback value in non-interactive context: no" | ||
| `); | ||
| expect(sentryRequests?.count).toEqual(0); | ||
| }); | ||
| }); | ||
| describe("interactive", () => { | ||
| beforeEach(() => { | ||
| setIsTTY(true); | ||
| }); | ||
| afterEach(() => { | ||
| setIsTTY(false); | ||
| }); | ||
| it("should not hit sentry in normal usage", async () => { | ||
| await runWrangler("version"); | ||
| expect(sentryRequests?.count).toEqual(0); | ||
| }); | ||
| it("should not hit sentry after error when permission denied", async () => { | ||
| mockConfirm({ | ||
| text: "Would you like to report this error to Cloudflare?", | ||
| result: false, | ||
| }); | ||
| await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( | ||
| `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` | ||
| ); | ||
| expect(std.out).toMatchInlineSnapshot(` | ||
| " | ||
| [32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose[0m" | ||
| `); | ||
| expect(sentryRequests?.count).toEqual(0); | ||
| }); | ||
| it("should hit sentry after error when permission provided", async () => { | ||
| mockConfirm({ | ||
| text: "Would you like to report this error to Cloudflare?", | ||
| result: true, | ||
| }); | ||
| await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( | ||
| `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` | ||
| ); | ||
| expect(std.out).toMatchInlineSnapshot(` | ||
| " | ||
| [32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose[0m" | ||
| `); | ||
| // Sentry sends multiple HTTP requests to capture breadcrumbs | ||
| expect(sentryRequests?.count).toBeGreaterThan(0); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| function mockSentryEndpoint() { | ||
| const requests = { count: 0 }; | ||
| msw.use( | ||
| rest.post( | ||
| `https://platform.dash.cloudflare.com/sentry/envelope`, | ||
| async (req, res, cxt) => { | ||
| requests.count++; | ||
| return res(cxt.status(200), cxt.json({})); | ||
| } | ||
| ) | ||
| ); | ||
|
|
||
| return requests; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import * as Sentry from "@sentry/node"; | ||
| import { rejectedSyncPromise } from "@sentry/utils"; | ||
| import { fetch } from "undici"; | ||
| import { version as wranglerVersion } from "../../package.json"; | ||
| import { confirm } from "../dialogs"; | ||
| import { logger } from "../logger"; | ||
| import type { BaseTransportOptions, TransportRequest } from "@sentry/types"; | ||
| import type { RequestInit } from "undici"; | ||
|
|
||
| let sentryReportingAllowed = false; | ||
|
|
||
| // The SENTRY_DSN is provided at esbuild time as a `define` for production and beta releases. | ||
| // Otherwise it is left undefined, which disables reporting. | ||
| declare const SENTRY_DSN: string; | ||
|
|
||
| /* Returns a Sentry transport for the Sentry proxy Worker. */ | ||
| export const makeSentry10Transport = (options: BaseTransportOptions) => { | ||
| let eventQueue: [string, RequestInit][] = []; | ||
|
|
||
| const transportSentry10 = async (request: TransportRequest) => { | ||
| /* Adds helpful properties to the request body before we send it to our | ||
| proxy Worker. These properties can be parsed out from the NDJSON in | ||
petebacondarwin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| `request.body`, but it's easier and safer to just attach them here. */ | ||
| const sentryWorkerPayload = { | ||
| envelope: request.body, | ||
| url: options.url, | ||
| }; | ||
|
|
||
| try { | ||
| if (sentryReportingAllowed) { | ||
| const eventsToSend = [...eventQueue]; | ||
| eventQueue = []; | ||
| for (const event of eventsToSend) { | ||
| await fetch(event[0], event[1]); | ||
| } | ||
penalosa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const response = await fetch( | ||
| `https://platform.dash.cloudflare.com/sentry/envelope`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| Accept: "*/*", | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify(sentryWorkerPayload), | ||
| } | ||
| ); | ||
|
|
||
| return { | ||
| statusCode: response.status, | ||
| headers: { | ||
| "x-sentry-rate-limits": response.headers.get( | ||
| "X-Sentry-Rate-Limits" | ||
| ), | ||
| "retry-after": response.headers.get("Retry-After"), | ||
| }, | ||
| }; | ||
| } else { | ||
| // We don't currently have permission to send this event, but maybe we will in the future. | ||
| // Add to an in-memory just in case | ||
| eventQueue.push([ | ||
| `https://platform.dash.cloudflare.com/sentry/envelope`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| Accept: "*/*", | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify(sentryWorkerPayload), | ||
| }, | ||
| ]); | ||
| return { | ||
| statusCode: 200, | ||
| }; | ||
| } | ||
| } catch (err) { | ||
| console.log(err); | ||
|
|
||
| return rejectedSyncPromise(err); | ||
| } | ||
| }; | ||
|
|
||
| return Sentry.createTransport(options, transportSentry10); | ||
| }; | ||
|
|
||
| export function setupSentry() { | ||
| if (typeof SENTRY_DSN !== "undefined") { | ||
| Sentry.init({ | ||
| release: `wrangler@${wranglerVersion}`, | ||
| dsn: SENTRY_DSN, | ||
| transport: makeSentry10Transport, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| export function addBreadcrumb( | ||
| message: string, | ||
| level: Sentry.SeverityLevel = "log" | ||
| ) { | ||
| if (typeof SENTRY_DSN !== "undefined") { | ||
| Sentry.addBreadcrumb({ | ||
| message, | ||
| level, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Capture top-level Wrangler errors. Also take this opportunity to ask the user for | ||
| // consent if not already granted. | ||
| export async function captureGlobalException(e: unknown) { | ||
| if (typeof SENTRY_DSN !== "undefined") { | ||
| sentryReportingAllowed = await confirm( | ||
| "Would you like to report this error to Cloudflare?", | ||
| { fallbackValue: false } | ||
| ); | ||
|
|
||
| if (!sentryReportingAllowed) { | ||
| logger.debug(`Sentry: Reporting disabled - would have sent ${e}.`); | ||
| return; | ||
| } | ||
|
|
||
| logger.debug(`Sentry: Capturing exception ${e}`); | ||
| Sentry.captureException(e); | ||
| } | ||
| } | ||
|
|
||
| // Ensure we send Sentry events before Wrangler exits | ||
| export async function closeSentry() { | ||
| if (typeof SENTRY_DSN !== "undefined") { | ||
| await Sentry.close(); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.