fix(logging): make logger import browser-safe

This commit is contained in:
Altay 2026-03-16 23:08:21 +03:00
parent 546e4d940a
commit df3a19051d
No known key found for this signature in database
2 changed files with 103 additions and 2 deletions

View File

@ -0,0 +1,70 @@
import { afterEach, describe, expect, it, vi } from "vitest";
type LoggerModule = typeof import("./logger.js");
const originalGetBuiltinModule = (
process as NodeJS.Process & { getBuiltinModule?: (id: string) => unknown }
).getBuiltinModule;
async function importBrowserSafeLogger(params?: {
resolvePreferredOpenClawTmpDir?: ReturnType<typeof vi.fn>;
}): Promise<{
module: LoggerModule;
resolvePreferredOpenClawTmpDir: ReturnType<typeof vi.fn>;
}> {
vi.resetModules();
const resolvePreferredOpenClawTmpDir =
params?.resolvePreferredOpenClawTmpDir ??
vi.fn(() => {
throw new Error("resolvePreferredOpenClawTmpDir should not run during browser-safe import");
});
vi.doMock("../infra/tmp-openclaw-dir.js", async () => {
const actual = await vi.importActual<typeof import("../infra/tmp-openclaw-dir.js")>(
"../infra/tmp-openclaw-dir.js",
);
return {
...actual,
resolvePreferredOpenClawTmpDir,
};
});
Object.defineProperty(process, "getBuiltinModule", {
configurable: true,
value: undefined,
});
const module = await import("./logger.js");
return { module, resolvePreferredOpenClawTmpDir };
}
describe("logging/logger browser-safe import", () => {
afterEach(() => {
vi.resetModules();
vi.doUnmock("../infra/tmp-openclaw-dir.js");
Object.defineProperty(process, "getBuiltinModule", {
configurable: true,
value: originalGetBuiltinModule,
});
});
it("does not resolve the preferred temp dir at import time when node fs is unavailable", async () => {
const { module, resolvePreferredOpenClawTmpDir } = await importBrowserSafeLogger();
expect(resolvePreferredOpenClawTmpDir).not.toHaveBeenCalled();
expect(module.DEFAULT_LOG_DIR).toBe("/tmp/openclaw");
expect(module.DEFAULT_LOG_FILE).toBe("/tmp/openclaw/openclaw.log");
});
it("disables file logging when imported in a browser-like environment", async () => {
const { module, resolvePreferredOpenClawTmpDir } = await importBrowserSafeLogger();
expect(module.getResolvedLoggerSettings()).toMatchObject({
level: "silent",
file: "/tmp/openclaw/openclaw.log",
});
expect(module.isFileLogLevelEnabled("info")).toBe(false);
expect(() => module.getLogger().info("browser-safe")).not.toThrow();
expect(resolvePreferredOpenClawTmpDir).not.toHaveBeenCalled();
});
});

View File

@ -3,7 +3,10 @@ import path from "node:path";
import { Logger as TsLogger } from "tslog";
import { getCommandPathWithRootOptions } from "../cli/argv.js";
import type { OpenClawConfig } from "../config/types.js";
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import {
POSIX_OPENCLAW_TMP_DIR,
resolvePreferredOpenClawTmpDir,
} from "../infra/tmp-openclaw-dir.js";
import { readLoggingConfig } from "./config.js";
import type { ConsoleStyle } from "./console.js";
import { resolveEnvLogLevelOverride } from "./env-log-level.js";
@ -12,7 +15,27 @@ import { resolveNodeRequireFromMeta } from "./node-require.js";
import { loggingState } from "./state.js";
import { formatLocalIsoWithOffset } from "./timestamps.js";
export const DEFAULT_LOG_DIR = resolvePreferredOpenClawTmpDir();
type ProcessWithBuiltinModule = NodeJS.Process & {
getBuiltinModule?: (id: string) => unknown;
};
function canUseNodeFs(): boolean {
const getBuiltinModule = (process as ProcessWithBuiltinModule).getBuiltinModule;
if (typeof getBuiltinModule !== "function") {
return false;
}
try {
return getBuiltinModule("fs") !== undefined;
} catch {
return false;
}
}
function resolveDefaultLogDir(): string {
return canUseNodeFs() ? resolvePreferredOpenClawTmpDir() : POSIX_OPENCLAW_TMP_DIR;
}
export const DEFAULT_LOG_DIR = resolveDefaultLogDir();
export const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "openclaw.log"); // legacy single-file path
const LOG_PREFIX = "openclaw";
@ -71,6 +94,14 @@ function canUseSilentVitestFileLogFastPath(envLevel: LogLevel | undefined): bool
}
function resolveSettings(): ResolvedSettings {
if (!canUseNodeFs()) {
return {
level: "silent",
file: DEFAULT_LOG_FILE,
maxFileBytes: DEFAULT_MAX_LOG_FILE_BYTES,
};
}
const envLevel = resolveEnvLogLevelOverride();
// Test runs default file logs to silent. Skip config reads and fallback load in the
// common case to avoid pulling heavy config/schema stacks on startup.