mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(logs): find active log file across date boundaries (#42904)
* fix(logs): find active log file across date boundaries Fixes #42875 When gateway runs across midnight, openclaw channels logs was looking for today's log file instead of the active one. This change makes the CLI find the most recently modified log file as a fallback. (cherry picked from commitfba6b88e86) * fix(channels): resolve active log file for channel logs (cherry picked from commitee87397a43) --------- Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/QA: prebuild the private QA channel runtime before plugin gauntlet source runs so wrapper CPU/RSS measurements are not polluted by private QA dist rebuild work. Thanks @vincentkoc.
|
||||
- Gateway/reload: bound default restart deferral and SIGUSR1 restart drain to five minutes while preserving explicit `deferralTimeoutMs: 0` indefinite waits, so stale active work accounting cannot block config reloads forever. Thanks @vincentkoc.
|
||||
- Active Memory: register the prompt-build hook with the configured recall timeout plus setup grace instead of the 150s maximum budget, so default memory recall cannot delay turn startup for multiple minutes. Thanks @vincentkoc.
|
||||
- CLI/channels logs: reuse the rolling log-file resolver so `openclaw channels logs` falls back to the active dated log across date boundaries without reading unrelated custom log files. Fixes #42875; carries forward #42904 and #43043. Thanks @ethanclaw and @wdskuki.
|
||||
- Security/audit: recognize dangerous node command IDs as valid `gateway.nodes.denyCommands` entries, so audit only warns on real typos or unsupported patterns. (#56923) Thanks @chziyue.
|
||||
- Telegram/exec approvals: stop treating general Telegram chat allowlists and `defaultTo` routes as native exec approvers; Telegram now uses explicit `execApprovals.approvers` or owner identity from `commands.ownerAllowFrom`, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.
|
||||
- Chat commands: route sensitive group `/diagnostics` and `/export-trajectory` approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.
|
||||
|
||||
@@ -37,6 +37,14 @@ function logLine(params: { module: string; message: string }) {
|
||||
});
|
||||
}
|
||||
|
||||
function readJsonPayload() {
|
||||
return JSON.parse(String(runtime.log.mock.calls[0]?.[0])) as {
|
||||
file: string;
|
||||
channel: string;
|
||||
lines: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
describe("channelsLogsCommand", () => {
|
||||
let tempDir: string;
|
||||
let logPath: string;
|
||||
@@ -75,11 +83,78 @@ describe("channelsLogsCommand", () => {
|
||||
includeDisabled: true,
|
||||
}),
|
||||
);
|
||||
const payload = JSON.parse(String(runtime.log.mock.calls[0]?.[0])) as {
|
||||
channel: string;
|
||||
lines: Array<{ message: string }>;
|
||||
};
|
||||
const payload = readJsonPayload();
|
||||
expect(payload.channel).toBe("external-chat");
|
||||
expect(payload.lines.map((line) => line.message)).toEqual(["external sent"]);
|
||||
});
|
||||
|
||||
it("falls back to the latest rolling log when the configured rolling file is missing", async () => {
|
||||
const configuredFile = path.join(tempDir, "openclaw-2026-04-26.log");
|
||||
const fallbackFile = path.join(tempDir, "openclaw-2026-04-25.log");
|
||||
const staleFile = path.join(tempDir, "openclaw-2026-04-24.log");
|
||||
setLoggerOverride({ file: configuredFile });
|
||||
await fs.writeFile(
|
||||
fallbackFile,
|
||||
[
|
||||
logLine({ module: "gateway/channels/slack/send", message: "slack fallback" }),
|
||||
logLine({ module: "gateway/channels/external-chat/send", message: "fallback sent" }),
|
||||
].join("\n"),
|
||||
);
|
||||
await fs.writeFile(
|
||||
staleFile,
|
||||
logLine({ module: "gateway/channels/external-chat/send", message: "stale sent" }),
|
||||
);
|
||||
await fs.utimes(
|
||||
staleFile,
|
||||
new Date("2026-04-24T12:00:00.000Z"),
|
||||
new Date("2026-04-24T12:00:00.000Z"),
|
||||
);
|
||||
await fs.utimes(
|
||||
fallbackFile,
|
||||
new Date("2026-04-25T12:00:00.000Z"),
|
||||
new Date("2026-04-25T12:00:00.000Z"),
|
||||
);
|
||||
|
||||
await channelsLogsCommand({ channel: "external-chat", json: true }, runtime);
|
||||
|
||||
const payload = readJsonPayload();
|
||||
expect(payload.file).toBe(fallbackFile);
|
||||
expect(payload.lines.map((line) => line.message)).toEqual(["fallback sent"]);
|
||||
});
|
||||
|
||||
it("prefers the configured rolling log when it exists", async () => {
|
||||
const configuredFile = path.join(tempDir, "openclaw-2026-04-26.log");
|
||||
const fallbackFile = path.join(tempDir, "openclaw-2026-04-25.log");
|
||||
setLoggerOverride({ file: configuredFile });
|
||||
await fs.writeFile(
|
||||
fallbackFile,
|
||||
logLine({ module: "gateway/channels/external-chat/send", message: "fallback sent" }),
|
||||
);
|
||||
await fs.writeFile(
|
||||
configuredFile,
|
||||
logLine({ module: "gateway/channels/external-chat/send", message: "current sent" }),
|
||||
);
|
||||
|
||||
await channelsLogsCommand({ channel: "external-chat", json: true }, runtime);
|
||||
|
||||
const payload = readJsonPayload();
|
||||
expect(payload.file).toBe(configuredFile);
|
||||
expect(payload.lines.map((line) => line.message)).toEqual(["current sent"]);
|
||||
});
|
||||
|
||||
it("does not fall back to rolling logs for a missing custom log file", async () => {
|
||||
const configuredFile = path.join(tempDir, "custom-channel.log");
|
||||
const fallbackFile = path.join(tempDir, "openclaw-2026-04-25.log");
|
||||
setLoggerOverride({ file: configuredFile });
|
||||
await fs.writeFile(
|
||||
fallbackFile,
|
||||
logLine({ module: "gateway/channels/external-chat/send", message: "fallback sent" }),
|
||||
);
|
||||
|
||||
await channelsLogsCommand({ channel: "external-chat", json: true }, runtime);
|
||||
|
||||
const payload = readJsonPayload();
|
||||
expect(payload.file).toBe(configuredFile);
|
||||
expect(payload.lines).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { normalizeChannelId as normalizeBundledChannelId } from "../../channels/registry.js";
|
||||
import { getResolvedLoggerSettings } from "../../logging.js";
|
||||
import { resolveLogFile } from "../../logging/log-tail.js";
|
||||
import { parseLogLine } from "../../logging/parse-log-line.js";
|
||||
import {
|
||||
listPluginContributionIds,
|
||||
@@ -103,7 +104,7 @@ export async function channelsLogsCommand(
|
||||
? Math.floor(limitRaw)
|
||||
: DEFAULT_LIMIT;
|
||||
|
||||
const file = getResolvedLoggerSettings().file;
|
||||
const file = await resolveLogFile(getResolvedLoggerSettings().file);
|
||||
const rawLines = await readTailLines(file, limit * 4);
|
||||
const parsed = rawLines
|
||||
.map(parseLogLine)
|
||||
|
||||
@@ -23,7 +23,7 @@ function isRollingLogFile(file: string): boolean {
|
||||
return ROLLING_LOG_RE.test(path.basename(file));
|
||||
}
|
||||
|
||||
async function resolveLogFile(file: string): Promise<string> {
|
||||
export async function resolveLogFile(file: string): Promise<string> {
|
||||
const stat = await fs.stat(file).catch(() => null);
|
||||
if (stat) {
|
||||
return file;
|
||||
|
||||
Reference in New Issue
Block a user