mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix(cli): fall back to file logs when local logs rpc closes
This commit is contained in:
@@ -15,6 +15,8 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- MCP/plugins: stringify non-array plugin tool results with chat-content coercion instead of default object stringification, so MCP callers receive useful JSON/text content from plugin tools. Thanks @vincentkoc.
|
||||
- CLI/logs: fall back to the configured Gateway file log when implicit loopback Gateway connections close or time out before or during `logs.tail`, so `openclaw logs` still works while diagnosing local-model Gateway disconnects. Refs #74078. Thanks @sakalaboator.
|
||||
- MCP/plugins: stringify non-array plugin tool results with chat-content coercion instead of default object stringification, so MCP callers receive useful JSON/text content from plugin tools. Thanks @vincentkoc.
|
||||
- Channels/Discord: remove Discord-owned queued-run timeout replies through the shared channel lifecycle queue while preserving message ordering and compatibility timeout constants, so long Discord turns stay governed by session/tool/runtime lifecycle instead of channel fallback errors. Thanks @codexGW.
|
||||
- Agents/tools: clamp `process.poll` waits to 30 seconds, advertise that cap in the tool schema, and honor abort signals while waiting, so long command polls cannot pin agent responsiveness after cancellation. Thanks @vincentkoc.
|
||||
|
||||
@@ -56,7 +56,7 @@ openclaw logs --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
|
||||
## Notes
|
||||
|
||||
- Use `--local-time` to render timestamps in your local timezone.
|
||||
- If the local loopback Gateway asks for pairing, `openclaw logs` falls back to the configured local log file automatically. Explicit `--url` targets do not use this fallback.
|
||||
- If the implicit local loopback Gateway asks for pairing, closes during connect, or times out before `logs.tail` answers, `openclaw logs` falls back to the configured Gateway file log automatically. Explicit `--url` targets do not use this fallback.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -73,9 +73,10 @@ In JSON mode, the CLI emits `type`-tagged objects:
|
||||
- `notice`: truncation / rotation hints
|
||||
- `raw`: unparsed log line
|
||||
|
||||
If the local loopback Gateway asks for pairing, `openclaw logs` falls back to
|
||||
the configured local log file automatically. Explicit `--url` targets do not
|
||||
use this fallback.
|
||||
If the implicit local loopback Gateway asks for pairing, closes during connect,
|
||||
or times out before `logs.tail` answers, `openclaw logs` falls back to the
|
||||
configured Gateway file log automatically. Explicit `--url` targets do not use
|
||||
this fallback.
|
||||
|
||||
If the Gateway is unreachable, the CLI prints a short hint to run:
|
||||
|
||||
|
||||
@@ -181,6 +181,46 @@ describe("logs cli", () => {
|
||||
expect(stderrWrites.join("")).toContain("reading local log file instead");
|
||||
});
|
||||
|
||||
it("falls back to the configured Gateway file log on loopback gateway close errors", async () => {
|
||||
callGatewayFromCli.mockRejectedValueOnce(
|
||||
new Error("gateway closed (1000 normal closure): no close reason"),
|
||||
);
|
||||
readConfiguredLogTail.mockResolvedValueOnce({
|
||||
file: "/tmp/openclaw.log",
|
||||
cursor: 5,
|
||||
size: 5,
|
||||
lines: ["local fallback line"],
|
||||
truncated: false,
|
||||
reset: false,
|
||||
});
|
||||
|
||||
const stdoutWrites = captureStdoutWrites();
|
||||
const stderrWrites = captureStderrWrites();
|
||||
|
||||
await runLogsCli(["logs"]);
|
||||
|
||||
expect(readConfiguredLogTail).toHaveBeenCalledTimes(1);
|
||||
expect(stdoutWrites.join("")).toContain("local fallback line");
|
||||
expect(stderrWrites.join("")).toContain("Local Gateway RPC unavailable");
|
||||
});
|
||||
|
||||
it("does not use local fallback for explicit Gateway URLs", async () => {
|
||||
callGatewayFromCli.mockRejectedValueOnce(
|
||||
new Error("gateway closed (1000 normal closure): no close reason"),
|
||||
);
|
||||
|
||||
const stdoutWrites = captureStdoutWrites();
|
||||
const stderrWrites = captureStderrWrites();
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
||||
|
||||
await runLogsCli(["logs", "--url", "ws://127.0.0.1:18789"]);
|
||||
|
||||
expect(readConfiguredLogTail).not.toHaveBeenCalled();
|
||||
expect(stdoutWrites.join("")).not.toContain("local fallback line");
|
||||
expect(stderrWrites.join("")).toContain("Gateway not reachable");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
describe("formatLogTimestamp", () => {
|
||||
it("formats UTC timestamp in plain mode by default", () => {
|
||||
const result = formatLogTimestamp("2025-01-01T12:00:00.000Z");
|
||||
|
||||
@@ -49,7 +49,7 @@ type LogsCliOptions = {
|
||||
expectFinal?: boolean;
|
||||
};
|
||||
|
||||
const LOCAL_FALLBACK_NOTICE = "Gateway pairing required; reading local log file instead.";
|
||||
const LOCAL_FALLBACK_NOTICE = "Local Gateway RPC unavailable; reading configured file log instead.";
|
||||
|
||||
function parsePositiveInt(value: string | undefined, fallback: number): number {
|
||||
if (!value) {
|
||||
@@ -81,6 +81,7 @@ async function fetchLogs(
|
||||
if (!shouldUseLocalLogsFallback(opts, error)) {
|
||||
throw error;
|
||||
}
|
||||
// Match the Gateway logs.tail source when implicit local RPC is unavailable.
|
||||
return {
|
||||
...(await readConfiguredLogTail({ cursor, limit, maxBytes })),
|
||||
localFallback: true,
|
||||
@@ -97,7 +98,7 @@ function normalizeErrorMessage(error: unknown): string {
|
||||
|
||||
function shouldUseLocalLogsFallback(opts: LogsCliOptions, error: unknown): boolean {
|
||||
const message = normalizeLowercaseStringOrEmpty(normalizeErrorMessage(error));
|
||||
if (!readConnectPairingRequiredMessage(message)) {
|
||||
if (!isLocalGatewayRpcUnavailableError(message)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof opts.url === "string" && opts.url.trim().length > 0) {
|
||||
@@ -114,6 +115,17 @@ function shouldUseLocalLogsFallback(opts: LogsCliOptions, error: unknown): boole
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalGatewayRpcUnavailableError(message: string): boolean {
|
||||
if (readConnectPairingRequiredMessage(message)) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
message.includes("gateway closed (") ||
|
||||
message.includes("gateway timeout after") ||
|
||||
message.includes("gateway connect failed:")
|
||||
);
|
||||
}
|
||||
|
||||
export function formatLogTimestamp(
|
||||
value?: string,
|
||||
mode: "pretty" | "plain" = "plain",
|
||||
|
||||
Reference in New Issue
Block a user