mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 05:00:25 +00:00
refactor(cli): separate json payload output from logging
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
setConsoleTimestampPrefix,
|
||||
setLoggerOverride,
|
||||
} from "../logging.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import {
|
||||
captureConsoleSnapshot,
|
||||
@@ -101,7 +102,7 @@ describe("enableConsoleCapture", () => {
|
||||
expect(warn).toHaveBeenCalledWith("12:34:56 [exec] hello");
|
||||
});
|
||||
|
||||
it("leaves JSON output unchanged when timestamp prefix is enabled", () => {
|
||||
it("prefixes JSON console output when timestamp prefix is enabled", () => {
|
||||
setLoggerOverride({ level: "info", file: tempLogPath() });
|
||||
const log = vi.fn();
|
||||
console.log = log;
|
||||
@@ -109,7 +110,24 @@ describe("enableConsoleCapture", () => {
|
||||
enableConsoleCapture();
|
||||
const payload = JSON.stringify({ ok: true });
|
||||
console.log(payload);
|
||||
expect(log).toHaveBeenCalledWith(payload);
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
const firstArg = String(log.mock.calls[0]?.[0] ?? "");
|
||||
expect(firstArg).toMatch(/^(?:\d{2}:\d{2}:\d{2}|\d{4}-\d{2}-\d{2}T)/);
|
||||
expect(firstArg.endsWith(` ${payload}`)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps diagnostics on stderr while runtime JSON stays on stdout", () => {
|
||||
setLoggerOverride({ level: "info", file: tempLogPath() });
|
||||
const stdoutWrite = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
const stderrWrite = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
routeLogsToStderr();
|
||||
enableConsoleCapture();
|
||||
|
||||
console.log("diag");
|
||||
defaultRuntime.writeJson({ ok: true });
|
||||
|
||||
expect(stderrWrite).toHaveBeenCalledWith("diag\n");
|
||||
expect(stdoutWrite).toHaveBeenCalledWith('{\n "ok": true\n}\n');
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -189,19 +189,6 @@ function hasTimestampPrefix(value: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isJsonPayload(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JSON.parse(trimmed);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route console.* calls through file logging while still emitting to stdout/stderr.
|
||||
* This keeps user-facing output unchanged but guarantees every console call is captured in log files.
|
||||
@@ -262,10 +249,7 @@ export function enableConsoleCapture(): void {
|
||||
}
|
||||
const trimmed = stripAnsi(formatted).trimStart();
|
||||
const shouldPrefixTimestamp =
|
||||
loggingState.consoleTimestampPrefix &&
|
||||
trimmed.length > 0 &&
|
||||
!hasTimestampPrefix(trimmed) &&
|
||||
!isJsonPayload(trimmed);
|
||||
loggingState.consoleTimestampPrefix && trimmed.length > 0 && !hasTimestampPrefix(trimmed);
|
||||
const timestamp = shouldPrefixTimestamp
|
||||
? formatConsoleTimestamp(getConsoleSettings().style)
|
||||
: "";
|
||||
@@ -288,9 +272,8 @@ export function enableConsoleCapture(): void {
|
||||
} catch {
|
||||
// never block console output on logging failures
|
||||
}
|
||||
if (loggingState.forceConsoleToStderr && !isJsonPayload(trimmed)) {
|
||||
// In --json mode, route diagnostic logs to stderr but let JSON
|
||||
// payloads (the actual command output) through to stdout via orig().
|
||||
if (loggingState.forceConsoleToStderr) {
|
||||
// In --json mode, all console.* writes are diagnostics and should stay off stdout.
|
||||
try {
|
||||
const line = timestamp ? `${timestamp} ${formatted}` : formatted;
|
||||
process.stderr.write(`${line}\n`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Chalk } from "chalk";
|
||||
import type { Logger as TsLogger } from "tslog";
|
||||
import { isVerbose } from "../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime, type OutputRuntimeEnv, type RuntimeEnv } from "../runtime.js";
|
||||
import { clearActiveProgressLine } from "../terminal/progress-line.js";
|
||||
import {
|
||||
formatConsoleTimestamp,
|
||||
@@ -404,7 +404,7 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger {
|
||||
export function runtimeForLogger(
|
||||
logger: SubsystemLogger,
|
||||
exit: RuntimeEnv["exit"] = defaultRuntime.exit,
|
||||
): RuntimeEnv {
|
||||
): OutputRuntimeEnv {
|
||||
const formatArgs = (...args: unknown[]) =>
|
||||
args
|
||||
.map((arg) => formatRuntimeArg(arg))
|
||||
@@ -413,6 +413,10 @@ export function runtimeForLogger(
|
||||
return {
|
||||
log: (...args: unknown[]) => logger.info(formatArgs(...args)),
|
||||
error: (...args: unknown[]) => logger.error(formatArgs(...args)),
|
||||
writeStdout: (value: string) => logger.info(value),
|
||||
writeJson: (value: unknown, space = 2) => {
|
||||
logger.info(JSON.stringify(value, null, space > 0 ? space : undefined));
|
||||
},
|
||||
exit,
|
||||
};
|
||||
}
|
||||
@@ -420,6 +424,6 @@ export function runtimeForLogger(
|
||||
export function createSubsystemRuntime(
|
||||
subsystem: string,
|
||||
exit: RuntimeEnv["exit"] = defaultRuntime.exit,
|
||||
): RuntimeEnv {
|
||||
): OutputRuntimeEnv {
|
||||
return runtimeForLogger(createSubsystemLogger(subsystem), exit);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user