Skip to content

Commit 0aa8fa5

Browse files
Support DO_NOT_TRACK=1 (#14924)
Co-authored-by: Edmund Hung <edmund@cloudflare.com>
1 parent 0cb72fb commit 0aa8fa5

11 files changed

Lines changed: 325 additions & 45 deletions

File tree

.changeset/nine-taxis-shave.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"create-cloudflare": minor
3+
---
4+
5+
Honor `DO_NOT_TRACK=1` as a telemetry opt-out
6+
7+
Create Cloudflare now disables telemetry when `DO_NOT_TRACK=1` is set, regardless of other telemetry settings.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@cloudflare/workers-utils": minor
3+
---
4+
5+
Add shared support for the `DO_NOT_TRACK` environment variable
6+
7+
Add utilities for recognizing `DO_NOT_TRACK=1` and incorporating it when resolving Wrangler's telemetry preference.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Honor `DO_NOT_TRACK=1` as a telemetry opt-out
6+
7+
Wrangler now disables telemetry when `DO_NOT_TRACK=1` is set, regardless of other telemetry settings.

packages/create-cloudflare/src/__tests__/metrics.test.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,42 @@ describe("createReporter", () => {
259259
expect(sendEvent).toHaveBeenCalledTimes(0);
260260
});
261261

262+
test("sends no event if the DO_NOT_TRACK env is set to '1'", async ({
263+
expect,
264+
}) => {
265+
vi.stubEnv("DO_NOT_TRACK", "1");
266+
267+
const deferred = promiseWithResolvers<string>();
268+
const reporter = createReporter();
269+
const operation = reporter.collectAsyncMetrics({
270+
eventPrefix: "c3 session",
271+
props: {
272+
args: {
273+
projectName: "app",
274+
},
275+
},
276+
promise: () => deferred.promise,
277+
});
278+
279+
expect(reporter.isEnabled).toBe(false);
280+
281+
expect(sendEvent).toHaveBeenCalledTimes(0);
282+
283+
deferred.resolve("test result");
284+
285+
await expect(operation).resolves.toBe("test result");
286+
expect(sendEvent).toHaveBeenCalledTimes(0);
287+
});
288+
289+
test("DO_NOT_TRACK='1' takes precedence over CREATE_CLOUDFLARE_TELEMETRY_DISABLED='0'", ({
290+
expect,
291+
}) => {
292+
vi.stubEnv("DO_NOT_TRACK", "1");
293+
vi.stubEnv("CREATE_CLOUDFLARE_TELEMETRY_DISABLED", "0");
294+
295+
expect(createReporter().isEnabled).toBe(false);
296+
});
297+
262298
test("sends started and cancelled event to sparrow if the promise reject with a CancelError", async ({
263299
expect,
264300
}) => {
@@ -531,6 +567,8 @@ describe("runTelemetryCommand", () => {
531567

532568
afterEach(() => {
533569
vi.useRealTimers();
570+
vi.clearAllMocks();
571+
vi.unstubAllEnvs();
534572
});
535573

536574
test("run telemetry status when c3permission is disabled", async ({
@@ -571,6 +609,36 @@ describe("runTelemetryCommand", () => {
571609
`);
572610
});
573611

612+
test("run telemetry status when DO_NOT_TRACK is enabled", ({ expect }) => {
613+
vi.stubEnv("DO_NOT_TRACK", "1");
614+
615+
runTelemetryCommand("status");
616+
617+
expect(readMetricsConfig).not.toHaveBeenCalled();
618+
expect(writeMetricsConfig).not.toHaveBeenCalled();
619+
expect(normalizeOutput(std.out)).toMatchInlineSnapshot(`
620+
"Status: Disabled (set by DO_NOT_TRACK)
621+
622+
"
623+
`);
624+
});
625+
626+
test("run telemetry status when CREATE_CLOUDFLARE_TELEMETRY_DISABLED is enabled", ({
627+
expect,
628+
}) => {
629+
vi.stubEnv("CREATE_CLOUDFLARE_TELEMETRY_DISABLED", "1");
630+
631+
runTelemetryCommand("status");
632+
633+
expect(readMetricsConfig).not.toHaveBeenCalled();
634+
expect(writeMetricsConfig).not.toHaveBeenCalled();
635+
expect(normalizeOutput(std.out)).toMatchInlineSnapshot(`
636+
"Status: Disabled (set by CREATE_CLOUDFLARE_TELEMETRY_DISABLED)
637+
638+
"
639+
`);
640+
});
641+
574642
test("run telemetry enable when c3permission is disabled", async ({
575643
expect,
576644
}) => {
@@ -618,6 +686,58 @@ describe("runTelemetryCommand", () => {
618686
`);
619687
});
620688

689+
test("run telemetry enable when DO_NOT_TRACK is enabled", ({ expect }) => {
690+
vi.stubEnv("DO_NOT_TRACK", "1");
691+
vi.mocked(readMetricsConfig).mockReturnValueOnce({
692+
c3permission: {
693+
enabled: false,
694+
date: new Date(),
695+
},
696+
});
697+
698+
runTelemetryCommand("enable");
699+
700+
expect(writeMetricsConfig).toHaveBeenCalledWith({
701+
c3permission: {
702+
enabled: true,
703+
date: new Date(),
704+
},
705+
});
706+
expect(normalizeOutput(std.out)).toMatchInlineSnapshot(`
707+
"Status: Disabled (set by DO_NOT_TRACK)
708+
709+
Telemetry has been enabled in Create-Cloudflare's global configuration, but remains disabled while DO_NOT_TRACK is set.
710+
"
711+
`);
712+
});
713+
714+
test("run telemetry enable when CREATE_CLOUDFLARE_TELEMETRY_DISABLED is enabled", ({
715+
expect,
716+
}) => {
717+
vi.stubEnv("CREATE_CLOUDFLARE_TELEMETRY_DISABLED", "1");
718+
vi.mocked(readMetricsConfig).mockReturnValueOnce({
719+
c3permission: {
720+
enabled: false,
721+
date: new Date(),
722+
},
723+
});
724+
725+
runTelemetryCommand("enable");
726+
727+
expect(writeMetricsConfig).toHaveBeenCalledWith({
728+
c3permission: {
729+
enabled: true,
730+
date: new Date(),
731+
},
732+
});
733+
expect(normalizeOutput(std.out)).toMatchInlineSnapshot(`
734+
"Status: Disabled (set by CREATE_CLOUDFLARE_TELEMETRY_DISABLED)
735+
736+
Telemetry has been enabled in Create-Cloudflare's global configuration, but remains disabled while CREATE_CLOUDFLARE_TELEMETRY_DISABLED is set.
737+
"
738+
`);
739+
});
740+
621741
test("run telemetry disable when c3permission is enabled", async ({
622742
expect,
623743
}) => {

packages/create-cloudflare/src/metrics.ts

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
22
import { setTimeout } from "node:timers/promises";
33
import { logRaw } from "@cloudflare/cli-shared-helpers";
44
import { CancelError } from "@cloudflare/cli-shared-helpers/error";
5+
import { isDoNotTrackEnabled } from "@cloudflare/workers-utils";
56
import {
67
getDeviceId,
78
readMetricsConfig,
@@ -58,6 +59,23 @@ export function getPlatform() {
5859
}
5960
}
6061

62+
function resolveTelemetryStatus():
63+
| { enabled: boolean; source: string }
64+
| undefined {
65+
if (isDoNotTrackEnabled()) {
66+
return { enabled: false, source: "DO_NOT_TRACK" };
67+
}
68+
69+
if (process.env.CREATE_CLOUDFLARE_TELEMETRY_DISABLED === "1") {
70+
return {
71+
enabled: false,
72+
source: "CREATE_CLOUDFLARE_TELEMETRY_DISABLED",
73+
};
74+
}
75+
76+
return undefined;
77+
}
78+
6179
export function createReporter() {
6280
const events: Array<Promise<void>> = [];
6381
const als = new AsyncLocalStorage<{
@@ -66,7 +84,9 @@ export function createReporter() {
6684

6785
const config = readMetricsConfig() ?? {};
6886
const isFirstUsage = config.c3permission === undefined;
69-
const isEnabled = isTelemetryEnabled();
87+
const isEnabled =
88+
resolveTelemetryStatus()?.enabled ??
89+
(sparrow.hasSparrowSourceKey() && getC3Permission(config).enabled);
7090
const deviceId = getDeviceId(config);
7191
const packageManager = detectPackageManager();
7292
const platform = getPlatform();
@@ -109,14 +129,6 @@ export function createReporter() {
109129
events.push(request);
110130
}
111131

112-
function isTelemetryEnabled() {
113-
if (process.env.CREATE_CLOUDFLARE_TELEMETRY_DISABLED === "1") {
114-
return false;
115-
}
116-
117-
return sparrow.hasSparrowSourceKey() && getC3Permission(config).enabled;
118-
}
119-
120132
async function waitForAllEventsSettled(): Promise<void> {
121133
await Promise.allSettled(events);
122134
}
@@ -292,8 +304,9 @@ function updateC3Permission(enabled: boolean) {
292304
writeMetricsConfig(config);
293305
}
294306

295-
function logTelemetryStatus(enabled: boolean) {
296-
logRaw(`Status: ${enabled ? "Enabled" : "Disabled"}`);
307+
function logTelemetryStatus(enabled: boolean, source?: string) {
308+
const sourceMessage = source === undefined ? "" : ` (set by ${source})`;
309+
logRaw(`Status: ${enabled ? "Enabled" : "Disabled"}${sourceMessage}`);
297310
logRaw("");
298311
}
299312

@@ -303,10 +316,19 @@ export const runTelemetryCommand = (
303316
switch (action) {
304317
case "enable": {
305318
updateC3Permission(true);
306-
logTelemetryStatus(true);
307-
logRaw(
308-
"Create-Cloudflare is now collecting telemetry about your usage. Thank you for helping us improve the experience!"
309-
);
319+
320+
const telemetry = resolveTelemetryStatus();
321+
if (telemetry === undefined) {
322+
logTelemetryStatus(true);
323+
logRaw(
324+
"Create-Cloudflare is now collecting telemetry about your usage. Thank you for helping us improve the experience!"
325+
);
326+
} else {
327+
logTelemetryStatus(telemetry.enabled, telemetry.source);
328+
logRaw(
329+
`Telemetry has been enabled in Create-Cloudflare's global configuration, but remains disabled while ${telemetry.source} is set.`
330+
);
331+
}
310332
break;
311333
}
312334
case "disable": {
@@ -316,9 +338,10 @@ export const runTelemetryCommand = (
316338
break;
317339
}
318340
case "status": {
319-
const telemetry = getC3Permission();
341+
const telemetry = resolveTelemetryStatus();
342+
const enabled = telemetry?.enabled ?? getC3Permission().enabled;
320343

321-
logTelemetryStatus(telemetry.enabled);
344+
logTelemetryStatus(enabled, telemetry?.source);
322345
break;
323346
}
324347
}

packages/create-cloudflare/telemetry.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ Alternatively, you can set an environment variable:
8282
export CREATE_CLOUDFLARE_TELEMETRY_DISABLED=1
8383
```
8484

85+
Create Cloudflare also honors the `DO_NOT_TRACK` environment variable. Set it to `1` to disable telemetry:
86+
87+
```sh
88+
export DO_NOT_TRACK=1
89+
```
90+
91+
`DO_NOT_TRACK=1` takes precedence over all other telemetry settings.
92+
8593
If you would like to re-enable telemetry, you can run:
8694

8795
```sh

packages/workers-utils/src/environment-variables/factory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ type VariableNames =
142142
/** Disable config watching in ConfigController. */
143143
| "WRANGLER_CI_DISABLE_CONFIG_WATCHING"
144144

145+
/** Disable telemetry when set to an opt-out value. */
146+
| "DO_NOT_TRACK"
147+
145148
// ## Docker Configuration
146149

147150
/** Path to docker binary (default: "docker"). */

packages/workers-utils/src/environment-variables/misc-variables.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,33 @@ export const getC3CommandFromEnv = getEnvironmentVariableFactory({
3737
defaultValue: () => "create cloudflare",
3838
});
3939

40-
/**
41-
* `WRANGLER_SEND_METRICS` can override whether we attempt to send metrics information to Sparrow.
42-
*/
43-
export const getWranglerSendMetricsFromEnv =
40+
const getDoNotTrackFromEnv = getEnvironmentVariableFactory({
41+
variableName: "DO_NOT_TRACK",
42+
});
43+
44+
/** Whether `DO_NOT_TRACK` is set to a supported telemetry opt-out value. */
45+
export function isDoNotTrackEnabled(): boolean {
46+
const value = getDoNotTrackFromEnv()?.toLowerCase();
47+
return value === "1" || value === "true";
48+
}
49+
50+
const getWranglerSendMetricsVariableFromEnv =
4451
getBooleanEnvironmentVariableFactory({
4552
variableName: "WRANGLER_SEND_METRICS",
4653
});
4754

55+
/**
56+
* `WRANGLER_SEND_METRICS` controls whether we attempt to send metrics information to Sparrow.
57+
* `DO_NOT_TRACK` takes precedence when it is set to an opt-out value.
58+
*/
59+
export function getWranglerSendMetricsFromEnv(): boolean | undefined {
60+
if (isDoNotTrackEnabled()) {
61+
return false;
62+
}
63+
64+
return getWranglerSendMetricsVariableFromEnv();
65+
}
66+
4867
/**
4968
* `WRANGLER_SEND_ERROR_REPORTS` controls whether we attempt to send error reports to Sentry.
5069
*

0 commit comments

Comments
 (0)