Skip to content

Commit f0c4b9e

Browse files
petebacondarwinAnka
authored andcommitted
allow WRANGLER_SEND_METRICS to override whether to report Wrangler crashes to Sentry
1 parent a879fe0 commit f0c4b9e

File tree

3 files changed

+341
-4
lines changed

3 files changed

+341
-4
lines changed

.changeset/angry-apes-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
allow WRANGLER_SEND_METRICS to override whether to report Wrangler crashes to Sentry

packages/wrangler/src/__tests__/sentry.test.ts

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,31 @@ describe("sentry", () => {
123123
expect(sentryRequests?.length).toEqual(0);
124124
});
125125

126+
it("should not hit sentry (or even ask) after reportable error if WRANGLER_SEND_METRICS is explicitly false", async () => {
127+
// Trigger an API error
128+
msw.use(
129+
http.get(
130+
`https://api.cloudflare.com/client/v4/user`,
131+
async () => {
132+
return HttpResponse.error();
133+
},
134+
{ once: true }
135+
),
136+
http.get("*/user/tokens/verify", () => {
137+
return HttpResponse.json(createFetchResult([]));
138+
})
139+
);
140+
await expect(
141+
runWrangler("whoami", { WRANGLER_SEND_METRICS: "false" })
142+
).rejects.toMatchInlineSnapshot(`[TypeError: Failed to fetch]`);
143+
expect(std.out).toMatchInlineSnapshot(`
144+
"Getting User settings...
145+
146+
If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose"
147+
`);
148+
expect(sentryRequests?.length).toEqual(0);
149+
});
150+
126151
it("should hit sentry after reportable error when permission provided", async () => {
127152
// Trigger an API error
128153
msw.use(
@@ -428,6 +453,308 @@ describe("sentry", () => {
428453
}
429454
`);
430455
});
456+
457+
it("should hit sentry after reportable error (without confirmation) if WRANGLER_SEND_METRICS is explicitly true", async () => {
458+
// Trigger an API error
459+
msw.use(
460+
http.get(
461+
`https://api.cloudflare.com/client/v4/user`,
462+
async () => {
463+
return HttpResponse.error();
464+
},
465+
{ once: true }
466+
),
467+
http.get("*/user/tokens/verify", () => {
468+
return HttpResponse.json(createFetchResult([]));
469+
})
470+
);
471+
await expect(
472+
runWrangler("whoami", { WRANGLER_SEND_METRICS: "true" })
473+
).rejects.toMatchInlineSnapshot(`[TypeError: Failed to fetch]`);
474+
expect(std.out).toMatchInlineSnapshot(`
475+
"Getting User settings...
476+
477+
If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose"
478+
`);
479+
480+
// Sentry sends multiple HTTP requests to capture breadcrumbs
481+
expect(sentryRequests?.length).toBeGreaterThan(0);
482+
assert(sentryRequests !== undefined);
483+
484+
// Check requests don't include PII
485+
const envelopes = sentryRequests.map(({ envelope }) => {
486+
const parts = envelope.split("\n").map((line) => JSON.parse(line));
487+
expect(parts).toHaveLength(3);
488+
return { header: parts[0], type: parts[1], data: parts[2] };
489+
});
490+
const event = envelopes.find(({ type }) => type.type === "event");
491+
assert(event !== undefined);
492+
493+
// Redact fields with random contents we know don't contain PII
494+
event.header.event_id = "";
495+
event.header.sent_at = "";
496+
event.header.trace.trace_id = "";
497+
event.header.trace.release = "";
498+
for (const exception of event.data.exception.values) {
499+
for (const frame of exception.stacktrace.frames) {
500+
if (
501+
frame.filename.startsWith("C:\\Project\\") ||
502+
frame.filename.startsWith("/project/")
503+
) {
504+
frame.filename = "/project/...";
505+
}
506+
frame.function = "";
507+
frame.lineno = 0;
508+
frame.colno = 0;
509+
frame.in_app = false;
510+
frame.pre_context = [];
511+
frame.context_line = "";
512+
frame.post_context = [];
513+
}
514+
}
515+
event.data.event_id = "";
516+
event.data.contexts.trace.trace_id = "";
517+
event.data.contexts.trace.span_id = "";
518+
event.data.contexts.runtime.version = "";
519+
event.data.contexts.app.app_start_time = "";
520+
event.data.contexts.app.app_memory = 0;
521+
event.data.contexts.os = {};
522+
event.data.contexts.device = {};
523+
event.data.timestamp = 0;
524+
event.data.release = "";
525+
for (const breadcrumb of event.data.breadcrumbs) {
526+
breadcrumb.timestamp = 0;
527+
}
528+
529+
const fakeInstallPath = "/wrangler/";
530+
for (const exception of event.data.exception?.values ?? []) {
531+
for (const frame of exception.stacktrace?.frames ?? []) {
532+
if (frame.module.startsWith("@mswjs")) {
533+
frame.module =
534+
"@mswjs.interceptors.src.interceptors.fetch:index.ts";
535+
}
536+
if (frame.filename === undefined) {
537+
continue;
538+
}
539+
540+
const wranglerPackageIndex = frame.filename.indexOf(
541+
path.join("packages", "wrangler", "src")
542+
);
543+
if (wranglerPackageIndex === -1) {
544+
continue;
545+
}
546+
frame.filename =
547+
fakeInstallPath +
548+
frame.filename
549+
.substring(wranglerPackageIndex)
550+
.replaceAll("\\", "/");
551+
continue;
552+
}
553+
}
554+
555+
// If more data is included in the Sentry request, we'll need to verify it
556+
// couldn't contain PII and update this snapshot
557+
expect(event).toStrictEqual({
558+
data: {
559+
breadcrumbs: [
560+
{
561+
level: "log",
562+
message: "wrangler whoami",
563+
timestamp: 0,
564+
},
565+
],
566+
contexts: {
567+
app: {
568+
app_memory: 0,
569+
app_start_time: "",
570+
},
571+
cloud_resource: {},
572+
device: {},
573+
os: {},
574+
runtime: {
575+
name: "node",
576+
version: "",
577+
},
578+
trace: {
579+
span_id: "",
580+
trace_id: "",
581+
},
582+
},
583+
environment: "production",
584+
event_id: "",
585+
exception: {
586+
values: [
587+
{
588+
mechanism: {
589+
handled: true,
590+
type: "generic",
591+
},
592+
stacktrace: {
593+
frames: [
594+
{
595+
colno: 0,
596+
context_line: "",
597+
filename: expect.any(String),
598+
function: "",
599+
in_app: false,
600+
lineno: 0,
601+
module: expect.any(String),
602+
post_context: [],
603+
pre_context: [],
604+
},
605+
{
606+
colno: 0,
607+
context_line: "",
608+
filename: expect.any(String),
609+
function: "",
610+
in_app: false,
611+
lineno: 0,
612+
module: expect.any(String),
613+
post_context: [],
614+
pre_context: [],
615+
},
616+
{
617+
colno: 0,
618+
context_line: "",
619+
filename: expect.any(String),
620+
function: "",
621+
in_app: false,
622+
lineno: 0,
623+
module: expect.any(String),
624+
post_context: [],
625+
pre_context: [],
626+
},
627+
{
628+
colno: 0,
629+
context_line: "",
630+
filename: expect.any(String),
631+
function: "",
632+
in_app: false,
633+
lineno: 0,
634+
module: expect.any(String),
635+
post_context: [],
636+
pre_context: [],
637+
},
638+
{
639+
colno: 0,
640+
context_line: "",
641+
filename: expect.any(String),
642+
function: "",
643+
in_app: false,
644+
lineno: 0,
645+
module: expect.any(String),
646+
post_context: [],
647+
pre_context: [],
648+
},
649+
{
650+
colno: 0,
651+
context_line: "",
652+
filename: expect.any(String),
653+
function: "",
654+
in_app: false,
655+
lineno: 0,
656+
module: expect.any(String),
657+
post_context: [],
658+
pre_context: [],
659+
},
660+
{
661+
colno: 0,
662+
context_line: "",
663+
filename: expect.any(String),
664+
function: "",
665+
in_app: false,
666+
lineno: 0,
667+
module: expect.any(String),
668+
post_context: [],
669+
pre_context: [],
670+
},
671+
{
672+
colno: 0,
673+
context_line: "",
674+
filename: expect.any(String),
675+
function: "",
676+
in_app: false,
677+
lineno: 0,
678+
module: expect.any(String),
679+
post_context: [],
680+
pre_context: [],
681+
},
682+
{
683+
colno: 0,
684+
context_line: "",
685+
filename: "/project/...",
686+
function: "",
687+
in_app: false,
688+
lineno: 0,
689+
module:
690+
"@mswjs.interceptors.src.interceptors.fetch:index.ts",
691+
post_context: [],
692+
pre_context: [],
693+
},
694+
{
695+
colno: 0,
696+
context_line: "",
697+
filename: "/project/...",
698+
function: "",
699+
in_app: false,
700+
lineno: 0,
701+
module:
702+
"@mswjs.interceptors.src.interceptors.fetch:index.ts",
703+
post_context: [],
704+
pre_context: [],
705+
},
706+
],
707+
},
708+
type: "TypeError",
709+
value: "Failed to fetch",
710+
},
711+
],
712+
},
713+
modules: {},
714+
platform: "node",
715+
release: "",
716+
sdk: {
717+
integrations: [
718+
"InboundFilters",
719+
"FunctionToString",
720+
"LinkedErrors",
721+
"Console",
722+
"OnUncaughtException",
723+
"OnUnhandledRejection",
724+
"ContextLines",
725+
"Context",
726+
"Modules",
727+
],
728+
name: "sentry.javascript.node",
729+
packages: [
730+
{
731+
name: "npm:@sentry/node",
732+
version: "7.87.0",
733+
},
734+
],
735+
version: "7.87.0",
736+
},
737+
timestamp: 0,
738+
},
739+
header: {
740+
event_id: "",
741+
sdk: {
742+
name: "sentry.javascript.node",
743+
version: "7.87.0",
744+
},
745+
sent_at: "",
746+
trace: {
747+
environment: "production",
748+
public_key: "9edbb8417b284aa2bbead9b4c318918b",
749+
release: "",
750+
trace_id: "",
751+
},
752+
},
753+
type: {
754+
type: "event",
755+
},
756+
});
757+
});
431758
});
432759
});
433760

packages/wrangler/src/sentry/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { rejectedSyncPromise } from "@sentry/utils";
33
import { fetch } from "undici";
44
import { version as wranglerVersion } from "../../package.json";
55
import { confirm } from "../dialogs";
6+
import { getWranglerSendMetricsFromEnv } from "../environment-variables/misc-variables";
67
import { logger } from "../logger";
78
import type { BaseTransportOptions, TransportRequest } from "@sentry/types";
89
import type { RequestInit } from "undici";
@@ -150,10 +151,14 @@ export function addBreadcrumb(
150151
// consent if not already granted.
151152
export async function captureGlobalException(e: unknown) {
152153
if (typeof SENTRY_DSN !== "undefined") {
153-
sentryReportingAllowed = await confirm(
154-
"Would you like to report this error to Cloudflare? Wrangler's output and the error details will be shared with the Wrangler team to help us diagnose and fix the issue.",
155-
{ fallbackValue: false }
156-
);
154+
const sendMetricsEnvVar = getWranglerSendMetricsFromEnv();
155+
sentryReportingAllowed =
156+
sendMetricsEnvVar !== undefined
157+
? sendMetricsEnvVar
158+
: await confirm(
159+
"Would you like to report this error to Cloudflare? Wrangler's output and the error details will be shared with the Wrangler team to help us diagnose and fix the issue.",
160+
{ fallbackValue: false }
161+
);
157162

158163
if (!sentryReportingAllowed) {
159164
logger.debug(`Sentry: Reporting disabled - would have sent ${e}.`);

0 commit comments

Comments
 (0)