Skip to content

Commit 410d985

Browse files
authored
Hide logs from startMixedModeSession (#9329)
* Hide logs from startMixedModeSession * Add E2E test to verify no double logging * Create selfish-numbers-play.md * hide teardown logs * add explanation
1 parent e5ae13a commit 410d985

File tree

7 files changed

+112
-23
lines changed

7 files changed

+112
-23
lines changed

.changeset/selfish-numbers-play.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+
Hide logs in the `startMixedModeSession()` API

packages/wrangler/e2e/dev-mixed-mode.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import dedent from "ts-dedent";
33
import { describe, expect, it } from "vitest";
44
import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test";
55
import { fetchText } from "./helpers/fetch-text";
6+
import { normalizeOutput } from "./helpers/normalize";
67
import { makeRoot, seed } from "./helpers/setup";
78

89
describe("wrangler dev - mixed mode", () => {
@@ -92,6 +93,60 @@ describe("wrangler dev - mixed mode", () => {
9293
`);
9394
});
9495

96+
it("doesn't show any logs from startMixedModeSession()", async () => {
97+
const helper = new WranglerE2ETestHelper();
98+
await spawnLocalWorker(helper);
99+
await helper.seed({
100+
"wrangler.json": JSON.stringify({
101+
name: "mixed-mode-mixed-bindings-test",
102+
main: "index.js",
103+
compatibility_date: "2025-05-07",
104+
ai: {
105+
binding: "AI",
106+
},
107+
}),
108+
"index.js": dedent`
109+
export default {
110+
async fetch(request, env) {
111+
const messages = [
112+
{
113+
role: "user",
114+
// Doing snapshot testing against AI responses can be flaky, but this prompt generates the same output relatively reliably
115+
content: "Respond with the exact text 'This is a response from Workers AI.'. Do not include any other text",
116+
},
117+
];
118+
119+
const { response } = await env.AI.run("@hf/thebloke/zephyr-7b-beta-awq", {
120+
messages,
121+
});
122+
123+
return new Response(\`REMOTE<AI>: \${response}\n\`);
124+
}
125+
}`,
126+
});
127+
128+
const worker = helper.runLongLived("wrangler dev --x-mixed-mode");
129+
130+
const { url } = await worker.waitForReady();
131+
132+
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(`
133+
"REMOTE<AI>: "This is a response from Workers AI."
134+
"
135+
`);
136+
137+
// This should only include logs from the user Wrangler session (i.e. a single list of attached bindings, and only one ready message)
138+
expect(normalizeOutput(worker.currentOutput)).toMatchInlineSnapshot(`
139+
"Your Worker and resources are simulated locally via Miniflare. For more information, see: https://developers.cloudflare.com/workers/testing/local-development.
140+
Your Worker has access to the following bindings:
141+
- AI:
142+
- Name: AI [connected to remote resource]
143+
[wrangler:inf] Ready on http://localhost:<PORT>
144+
▲ [WARNING] Using Workers AI always accesses your Cloudflare account in order to run AI models, and so will incur usage charges even in local development.
145+
⎔ Starting local server...
146+
[wrangler:inf] GET / 200 OK (TIMINGS)"
147+
`);
148+
});
149+
95150
describe("multi-worker", () => {
96151
it("handles both remote and local service bindings at the same time in all workers", async () => {
97152
const helper = new WranglerE2ETestHelper();

packages/wrangler/e2e/helpers/normalize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function stripEmptyNewlines(stdout: string): string {
4343
}
4444

4545
function stripDevTimings(stdout: string): string {
46-
return stdout.replace(/\(\dms\)/g, "(TIMINGS)");
46+
return stdout.replace(/\(\d+ms\)/g, "(TIMINGS)");
4747
}
4848

4949
function removeWorkerPreviewUrl(str: string) {

packages/wrangler/src/api/mixedMode/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export async function startMixedModeSession(
4545
inspector: {
4646
port: await getPort(),
4747
},
48+
logLevel: "none",
4849
},
4950
bindings: rawBindings,
5051
});

packages/wrangler/src/api/startDevWorker/ConfigController.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { getClassNamesWhichUseSQLite } from "../../dev/class-names-sqlite";
1414
import { getLocalPersistencePath } from "../../dev/get-local-persistence-path";
1515
import { UserError } from "../../errors";
16-
import { logger } from "../../logger";
16+
import { logger, runWithLogLevel } from "../../logger";
1717
import { checkTypesDiff } from "../../type-generation/helpers";
1818
import { requireApiToken, requireAuth } from "../../user";
1919
import {
@@ -418,7 +418,9 @@ export class ConfigController extends Controller<ConfigControllerEventMap> {
418418
}
419419

420420
public set(input: StartDevWorkerInput, throwErrors = false) {
421-
return this.#updateConfig(input, throwErrors);
421+
return runWithLogLevel(input.dev?.logLevel, () =>
422+
this.#updateConfig(input, throwErrors)
423+
);
422424
}
423425
public patch(input: Partial<StartDevWorkerInput>) {
424426
assert(
@@ -431,7 +433,9 @@ export class ConfigController extends Controller<ConfigControllerEventMap> {
431433
...input,
432434
};
433435

434-
return this.#updateConfig(config);
436+
return runWithLogLevel(config.dev?.logLevel, () =>
437+
this.#updateConfig(config)
438+
);
435439
}
436440

437441
async #updateConfig(input: StartDevWorkerInput, throwErrors = false) {

packages/wrangler/src/api/startDevWorker/DevEnv.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from "node:assert";
22
import { EventEmitter } from "node:events";
3-
import { logger } from "../../logger";
3+
import { logger, runWithLogLevel } from "../../logger";
44
import { formatMessage, ParseError } from "../../parse";
55
import { BundlerController } from "./BundlerController";
66
import { ConfigController } from "./ConfigController";
@@ -94,23 +94,25 @@ export class DevEnv extends EventEmitter {
9494
// *********************
9595

9696
async teardown() {
97-
logger.debug("DevEnv teardown beginning...");
97+
await runWithLogLevel(this.config.latestInput?.dev?.logLevel, async () => {
98+
logger.debug("DevEnv teardown beginning...");
9899

99-
await Promise.all([
100-
this.config.teardown(),
101-
this.bundler.teardown(),
102-
...this.runtimes.map((runtime) => runtime.teardown()),
103-
this.proxy.teardown(),
104-
]);
100+
await Promise.all([
101+
this.config.teardown(),
102+
this.bundler.teardown(),
103+
...this.runtimes.map((runtime) => runtime.teardown()),
104+
this.proxy.teardown(),
105+
]);
105106

106-
this.config.removeAllListeners();
107-
this.bundler.removeAllListeners();
108-
this.runtimes.forEach((runtime) => runtime.removeAllListeners());
109-
this.proxy.removeAllListeners();
107+
this.config.removeAllListeners();
108+
this.bundler.removeAllListeners();
109+
this.runtimes.forEach((runtime) => runtime.removeAllListeners());
110+
this.proxy.removeAllListeners();
110111

111-
this.emit("teardown");
112+
this.emit("teardown");
112113

113-
logger.debug("DevEnv teardown complete");
114+
logger.debug("DevEnv teardown complete");
115+
});
114116
}
115117

116118
emitErrorEvent(ev: ErrorEvent) {

packages/wrangler/src/logger.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AsyncLocalStorage } from "node:async_hooks";
12
import { format } from "node:util";
23
import chalk from "chalk";
34
import CLITable from "cli-table3";
@@ -49,6 +50,22 @@ function getLoggerLevel(): LoggerLevel {
4950
return "log";
5051
}
5152

53+
const overrideLoggerLevel = new AsyncLocalStorage<{
54+
logLevel: LoggerLevel | undefined;
55+
}>();
56+
57+
/**
58+
* This function runs a callback with a specified log level
59+
* The provided log level is stored using AsyncLocalStorage, and will be used
60+
* for all logger.* calls that happen within the callback.
61+
*/
62+
export const runWithLogLevel = <V>(
63+
overrideLogLevel: LoggerLevel | undefined,
64+
cb: () => V
65+
) => overrideLoggerLevel.run({ logLevel: overrideLogLevel }, cb);
66+
67+
overrideLoggerLevel.getStore;
68+
5269
export type TableRow<Keys extends string> = Record<Keys, string>;
5370

5471
export class Logger {
@@ -58,7 +75,11 @@ export class Logger {
5875
private onceHistory = new Set<string>();
5976

6077
get loggerLevel() {
61-
return this.overrideLoggerLevel ?? getLoggerLevel();
78+
return (
79+
overrideLoggerLevel.getStore()?.logLevel ??
80+
this.overrideLoggerLevel ??
81+
getLoggerLevel()
82+
);
6283
}
6384

6485
set loggerLevel(val) {
@@ -106,10 +127,11 @@ export class Logger {
106127
if (typeof console[method] !== "function") {
107128
throw new Error(`console.${method}() is not a function`);
108129
}
109-
110-
Logger.#beforeLogHook?.();
111-
(console[method] as (...args: unknown[]) => unknown).apply(console, args);
112-
Logger.#afterLogHook?.();
130+
if (LOGGER_LEVELS[this.loggerLevel] !== LOGGER_LEVELS.none) {
131+
Logger.#beforeLogHook?.();
132+
(console[method] as (...args: unknown[]) => unknown).apply(console, args);
133+
Logger.#afterLogHook?.();
134+
}
113135
}
114136

115137
get once() {

0 commit comments

Comments
 (0)