Skip to content

Commit 93c4c26

Browse files
fix: strip ANSI escape codes from wrangler log files (#10209)
* fix: strip ANSI escape codes from wrangler log files - Modified appendToDebugLogFile to use stripAnsi() before writing to log files - Added comprehensive tests for ANSI stripping functionality - Log files will now be clean and readable without color escape sequences - Console output still retains colors as expected - Fixes issue #4492 Co-Authored-By: [email protected] <[email protected]> * test: update log-file tests to use runInTempDir() instead of mocking filesystem - Remove complex mocking approach for debugLogFilepath - Import debugLogFilepath directly and use real filesystem operations - Add better error handling in getLogFileContent() helper - Tests now verify actual log file creation and ANSI stripping with real files - All tests pass with runInTempDir() providing isolated test environments Co-Authored-By: [email protected] <[email protected]> * chore: add changeset for ANSI stripping feature Co-Authored-By: [email protected] <[email protected]> * test: use mockConsoleMethods() instead of logger mocking in log-file tests - Remove vi.mock() for logger and unused mockConsoleMethods import - Focus on testing core ANSI stripping functionality with real filesystem operations - All tests now pass locally with proper runInTempDir() usage Co-Authored-By: [email protected] <[email protected]> * test: remove unused afterEach import from log-file tests - Clean up test file after removing logger mocking - All tests pass locally with proper runInTempDir() usage - Formatting and linting checks pass Co-Authored-By: [email protected] <[email protected]> * fix: remove duplicate changeset file - Remove .changeset/weak-chicken-fetch.md as it duplicates .changeset/smooth-shirts-add.md - Both files had identical content for ANSI stripping feature - Addresses GitHub PR comment about duplicate changeset Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]>
1 parent b02843f commit 93c4c26

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

.changeset/smooth-shirts-add.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+
fix: strip ANSI escape codes from log files to improve readability and parsing
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { existsSync, readdirSync, readFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
import { beforeEach, describe, expect, it, vi } from "vitest";
4+
import { appendToDebugLogFile, debugLogFilepath } from "../../utils/log-file";
5+
import { runInTempDir } from "../helpers/run-in-tmp";
6+
7+
describe("appendToDebugLogFile", () => {
8+
runInTempDir();
9+
10+
beforeEach(() => {
11+
vi.stubEnv("WRANGLER_LOG_PATH", "logs");
12+
});
13+
14+
function getLogFileContent(): string {
15+
if (existsSync(debugLogFilepath)) {
16+
return readFileSync(debugLogFilepath, "utf8");
17+
}
18+
19+
if (existsSync("logs")) {
20+
const logFiles = readdirSync("logs");
21+
expect(logFiles.length).toBeGreaterThan(0);
22+
23+
const logFilePath = join("logs", logFiles[0]);
24+
return readFileSync(logFilePath, "utf8");
25+
}
26+
27+
throw new Error(
28+
`No log files found. debugLogFilepath: ${debugLogFilepath}, logs dir exists: ${existsSync("logs")}`
29+
);
30+
}
31+
32+
it("should strip ANSI escape codes from error messages", async () => {
33+
const messageWithAnsi = "\u001b[31mError: Something went wrong\u001b[0m";
34+
const expectedCleanMessage = "Error: Something went wrong";
35+
36+
await appendToDebugLogFile("error", messageWithAnsi);
37+
38+
const logContent = getLogFileContent();
39+
expect(logContent).toContain(expectedCleanMessage);
40+
expect(logContent).not.toContain("\u001b[31m");
41+
expect(logContent).not.toContain("\u001b[0m");
42+
});
43+
44+
it("should strip complex ANSI escape sequences", async () => {
45+
const messageWithComplexAnsi =
46+
"\u001b[1;32mSuccess:\u001b[0m \u001b[33mWarning text\u001b[0m";
47+
const expectedCleanMessage = "Success: Warning text";
48+
49+
await appendToDebugLogFile("log", messageWithComplexAnsi);
50+
51+
const logContent = getLogFileContent();
52+
expect(logContent).toContain(expectedCleanMessage);
53+
expect(logContent).not.toContain("\u001b[1;32m");
54+
expect(logContent).not.toContain("\u001b[33m");
55+
});
56+
57+
it("should preserve plain messages without ANSI codes", async () => {
58+
const plainMessage = "This is a plain log message";
59+
60+
await appendToDebugLogFile("info", plainMessage);
61+
62+
const logContent = getLogFileContent();
63+
expect(logContent).toContain(plainMessage);
64+
});
65+
66+
it("should handle multiline messages with ANSI codes", async () => {
67+
const multilineMessageWithAnsi =
68+
"\u001b[31mLine 1 with color\u001b[0m\nLine 2 plain\n\u001b[32mLine 3 with different color\u001b[0m";
69+
const expectedCleanMessage =
70+
"Line 1 with color\nLine 2 plain\nLine 3 with different color";
71+
72+
await appendToDebugLogFile("warn", multilineMessageWithAnsi);
73+
74+
const logContent = getLogFileContent();
75+
expect(logContent).toContain(expectedCleanMessage);
76+
expect(logContent).not.toContain("\u001b[31m");
77+
expect(logContent).not.toContain("\u001b[32m");
78+
});
79+
80+
it("should maintain timestamp and log level formatting", async () => {
81+
const message = "\u001b[31mTest message\u001b[0m";
82+
83+
await appendToDebugLogFile("error", message);
84+
85+
const logContent = getLogFileContent();
86+
expect(logContent).toMatch(
87+
/--- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z error/
88+
);
89+
expect(logContent).toContain("Test message");
90+
expect(logContent).toContain("---");
91+
});
92+
93+
it("should handle empty messages", async () => {
94+
await appendToDebugLogFile("debug", "");
95+
96+
const logContent = getLogFileContent();
97+
expect(logContent).toMatch(
98+
/--- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z debug/
99+
);
100+
});
101+
102+
it("should handle messages with only ANSI codes", async () => {
103+
const onlyAnsiMessage = "\u001b[31m\u001b[0m";
104+
105+
await appendToDebugLogFile("log", onlyAnsiMessage);
106+
107+
const logContent = getLogFileContent();
108+
expect(logContent).not.toContain("\u001b[31m");
109+
expect(logContent).not.toContain("\u001b[0m");
110+
expect(logContent).toMatch(
111+
/--- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z log/
112+
);
113+
});
114+
});

packages/wrangler/src/utils/log-file.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { appendFile } from "node:fs/promises";
22
import path from "node:path";
33
import { Mutex } from "miniflare";
44
import onExit from "signal-exit";
5+
import stripAnsi from "strip-ansi";
56
import { getEnvironmentVariableFactory } from "../environment-variables/factory";
67
import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
78
import { logger } from "../logger";
@@ -51,7 +52,7 @@ export async function appendToDebugLogFile(
5152
) {
5253
const entry = `
5354
--- ${new Date().toISOString()} ${messageLevel}
54-
${message}
55+
${stripAnsi(message)}
5556
---
5657
`;
5758

0 commit comments

Comments
 (0)