import { Miniflare, MiniflareCoreError } from "miniflare"; import { assert, test } from "vitest"; import { useDispose } from "./test-shared"; import type { WorkerdStructuredLog } from "miniflare"; test("logs are treated as standard stdout/stderr chunks by default", async ({ expect, }) => { const collected = { stdout: "", stderr: "", }; const mf = new Miniflare({ modules: true, handleRuntimeStdio(stdout, stderr) { stdout.forEach((data) => { collected.stdout += `${data}`; }); stderr.forEach((error) => { collected.stderr += `${error}`; }); }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.warn('__WARN__'); console.error('__ERROR__'); console.info('__INFO__'); console.debug('__DEBUG__'); return new Response('Hello world!'); } }`, }); useDispose(mf); const response = await mf.dispatchFetch("http://localhost"); await response.text(); expect(collected.stdout).toBe("__LOG__\n__INFO__\n__DEBUG__\n"); expect(collected.stderr).toBe("__WARN__\n__ERROR__\n"); }); test("logs are structured and all sent to stdout when `structuredWorkerdLogs` is `true`", async ({ expect, }) => { const collected = { stdout: "", stderr: "", }; const mf = new Miniflare({ modules: true, structuredWorkerdLogs: true, handleRuntimeStdio(stdout, stderr) { stdout.forEach((data) => { collected.stdout += `${data}`; }); stderr.forEach((error) => { collected.stderr += `${error}`; }); }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.warn('__WARN__'); console.error('__ERROR__'); console.info('__INFO__'); console.debug('__DEBUG__'); return new Response('Hello world!'); } }`, }); useDispose(mf); const response = await mf.dispatchFetch("http://localhost"); await response.text(); expect(collected.stdout).toMatch( /{"timestamp":\d+,"level":"log","message":"__LOG__"}/ ); expect(collected.stdout).toMatch( /{"timestamp":\d+,"level":"warn","message":"__WARN__"}/ ); expect(collected.stdout).toMatch( /{"timestamp":\d+,"level":"error","message":"__ERROR__"}/ ); expect(collected.stdout).toMatch( /{"timestamp":\d+,"level":"info","message":"__INFO__"}/ ); expect(collected.stdout).toMatch( /{"timestamp":\d+,"level":"debug","message":"__DEBUG__"}/ ); expect(collected.stderr).toBe(""); }); test("logs are structured and handled via `handleStructuredLogs` when such option is provided (no `structuredWorkerdLogs: true` needed)", async ({ expect, }) => { const collectedLogs: (Pick & { timestamp: string; })[] = []; const mf = new Miniflare({ modules: true, handleStructuredLogs(log) { collectedLogs.push({ ...log, timestamp: `<${typeof log.timestamp}>`, }); }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.warn('__WARN__'); console.error('__ERROR__'); console.info('__INFO__'); console.debug('__DEBUG__'); return new Response('Hello world!'); } }`, }); useDispose(mf); const response = await mf.dispatchFetch("http://localhost"); await response.text(); expect(collectedLogs).toEqual([ { level: "log", message: "__LOG__", timestamp: "", }, { level: "warn", message: "__WARN__", timestamp: "", }, { level: "error", message: "__ERROR__", timestamp: "", }, { level: "info", message: "__INFO__", timestamp: "", }, { level: "debug", message: "__DEBUG__", timestamp: "", }, ]); }); test("even when `handleStructuredLogs` is provided, `handleRuntimeStdio` can still be used to read the raw stream values", async ({ expect, }) => { let numOfCollectedStructuredLogs = 0; const collectedRaw = { stdout: "", stderr: "", }; const mf = new Miniflare({ modules: true, handleRuntimeStdio(stdout, stderr) { stdout.forEach((data) => { collectedRaw.stdout += `${data}`; }); stderr.forEach((error) => { collectedRaw.stderr += `${error}`; }); }, handleStructuredLogs() { numOfCollectedStructuredLogs++; }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.error('__ERROR__'); return new Response('Hello world!'); } }`, }); useDispose(mf); const response = await mf.dispatchFetch("http://localhost"); await response.text(); expect(numOfCollectedStructuredLogs).toBe(2); expect(collectedRaw.stdout).toMatch( /{"timestamp":\d+,"level":"log","message":"__LOG__"}/ ); expect(collectedRaw.stdout).toMatch( /{"timestamp":\d+,"level":"error","message":"__ERROR__"}/ ); expect(collectedRaw.stderr).toBe(""); }); test("setting `handleStructuredLogs` when `structuredWorkerdLogs` is `false` triggers an error", async ({ expect, }) => { const mf = new Miniflare({ modules: true, script: "" }); useDispose(mf); let error: MiniflareCoreError | undefined = undefined; try { new Miniflare({ modules: true, script: "", structuredWorkerdLogs: false, handleStructuredLogs() {}, }); } catch (e) { error = e as MiniflareCoreError; } assert(error instanceof MiniflareCoreError); expect(error.code).toBe("ERR_VALIDATION"); expect(error.message).toContain( "A `handleStructuredLogs` has been provided but `structuredWorkerdLogs` is set to `false`" ); }); test("when using `handleStructuredLogs` some known unhelpful logs are filtered out (e.g. CODE_MOVED warnings)", async ({ expect, }) => { const collectedLogs: (Pick & { timestamp: string; })[] = []; const mf = new Miniflare({ modules: true, handleStructuredLogs(log) { collectedLogs.push({ ...log, timestamp: `<${typeof log.timestamp}>`, }); }, script: ` export default { async fetch(req, env) { console.log('__LOG__'); console.error('CODE_MOVED for unknown code block'); console.error('__ERROR__'); return new Response('Hello world!'); } }`, }); useDispose(mf); const response = await mf.dispatchFetch("http://localhost"); await response.text(); expect(collectedLogs).toEqual([ { level: "log", message: "__LOG__", timestamp: "", }, { level: "error", message: "__ERROR__", timestamp: "", }, ]); });