Skip to content

Commit df3a190

Browse files
committed
fix(logging): make logger import browser-safe
1 parent 546e4d9 commit df3a190

2 files changed

Lines changed: 103 additions & 2 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
type LoggerModule = typeof import("./logger.js");
4+
5+
const originalGetBuiltinModule = (
6+
process as NodeJS.Process & { getBuiltinModule?: (id: string) => unknown }
7+
).getBuiltinModule;
8+
9+
async function importBrowserSafeLogger(params?: {
10+
resolvePreferredOpenClawTmpDir?: ReturnType<typeof vi.fn>;
11+
}): Promise<{
12+
module: LoggerModule;
13+
resolvePreferredOpenClawTmpDir: ReturnType<typeof vi.fn>;
14+
}> {
15+
vi.resetModules();
16+
const resolvePreferredOpenClawTmpDir =
17+
params?.resolvePreferredOpenClawTmpDir ??
18+
vi.fn(() => {
19+
throw new Error("resolvePreferredOpenClawTmpDir should not run during browser-safe import");
20+
});
21+
22+
vi.doMock("../infra/tmp-openclaw-dir.js", async () => {
23+
const actual = await vi.importActual<typeof import("../infra/tmp-openclaw-dir.js")>(
24+
"../infra/tmp-openclaw-dir.js",
25+
);
26+
return {
27+
...actual,
28+
resolvePreferredOpenClawTmpDir,
29+
};
30+
});
31+
32+
Object.defineProperty(process, "getBuiltinModule", {
33+
configurable: true,
34+
value: undefined,
35+
});
36+
37+
const module = await import("./logger.js");
38+
return { module, resolvePreferredOpenClawTmpDir };
39+
}
40+
41+
describe("logging/logger browser-safe import", () => {
42+
afterEach(() => {
43+
vi.resetModules();
44+
vi.doUnmock("../infra/tmp-openclaw-dir.js");
45+
Object.defineProperty(process, "getBuiltinModule", {
46+
configurable: true,
47+
value: originalGetBuiltinModule,
48+
});
49+
});
50+
51+
it("does not resolve the preferred temp dir at import time when node fs is unavailable", async () => {
52+
const { module, resolvePreferredOpenClawTmpDir } = await importBrowserSafeLogger();
53+
54+
expect(resolvePreferredOpenClawTmpDir).not.toHaveBeenCalled();
55+
expect(module.DEFAULT_LOG_DIR).toBe("/tmp/openclaw");
56+
expect(module.DEFAULT_LOG_FILE).toBe("/tmp/openclaw/openclaw.log");
57+
});
58+
59+
it("disables file logging when imported in a browser-like environment", async () => {
60+
const { module, resolvePreferredOpenClawTmpDir } = await importBrowserSafeLogger();
61+
62+
expect(module.getResolvedLoggerSettings()).toMatchObject({
63+
level: "silent",
64+
file: "/tmp/openclaw/openclaw.log",
65+
});
66+
expect(module.isFileLogLevelEnabled("info")).toBe(false);
67+
expect(() => module.getLogger().info("browser-safe")).not.toThrow();
68+
expect(resolvePreferredOpenClawTmpDir).not.toHaveBeenCalled();
69+
});
70+
});

src/logging/logger.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import path from "node:path";
33
import { Logger as TsLogger } from "tslog";
44
import { getCommandPathWithRootOptions } from "../cli/argv.js";
55
import type { OpenClawConfig } from "../config/types.js";
6-
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
6+
import {
7+
POSIX_OPENCLAW_TMP_DIR,
8+
resolvePreferredOpenClawTmpDir,
9+
} from "../infra/tmp-openclaw-dir.js";
710
import { readLoggingConfig } from "./config.js";
811
import type { ConsoleStyle } from "./console.js";
912
import { resolveEnvLogLevelOverride } from "./env-log-level.js";
@@ -12,7 +15,27 @@ import { resolveNodeRequireFromMeta } from "./node-require.js";
1215
import { loggingState } from "./state.js";
1316
import { formatLocalIsoWithOffset } from "./timestamps.js";
1417

15-
export const DEFAULT_LOG_DIR = resolvePreferredOpenClawTmpDir();
18+
type ProcessWithBuiltinModule = NodeJS.Process & {
19+
getBuiltinModule?: (id: string) => unknown;
20+
};
21+
22+
function canUseNodeFs(): boolean {
23+
const getBuiltinModule = (process as ProcessWithBuiltinModule).getBuiltinModule;
24+
if (typeof getBuiltinModule !== "function") {
25+
return false;
26+
}
27+
try {
28+
return getBuiltinModule("fs") !== undefined;
29+
} catch {
30+
return false;
31+
}
32+
}
33+
34+
function resolveDefaultLogDir(): string {
35+
return canUseNodeFs() ? resolvePreferredOpenClawTmpDir() : POSIX_OPENCLAW_TMP_DIR;
36+
}
37+
38+
export const DEFAULT_LOG_DIR = resolveDefaultLogDir();
1639
export const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "openclaw.log"); // legacy single-file path
1740

1841
const LOG_PREFIX = "openclaw";
@@ -71,6 +94,14 @@ function canUseSilentVitestFileLogFastPath(envLevel: LogLevel | undefined): bool
7194
}
7295

7396
function resolveSettings(): ResolvedSettings {
97+
if (!canUseNodeFs()) {
98+
return {
99+
level: "silent",
100+
file: DEFAULT_LOG_FILE,
101+
maxFileBytes: DEFAULT_MAX_LOG_FILE_BYTES,
102+
};
103+
}
104+
74105
const envLevel = resolveEnvLogLevelOverride();
75106
// Test runs default file logs to silent. Skip config reads and fallback load in the
76107
// common case to avoid pulling heavy config/schema stacks on startup.

0 commit comments

Comments
 (0)