mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 11:37:26 +00:00
fix: use local timezone in console log timestamps
formatConsoleTimestamp previously used Date.toISOString() which always returns UTC time (suffixed with Z). This confused users whose local timezone differs from UTC. Now uses local time methods (getHours, getMinutes, etc.) and appends the local UTC offset (e.g. +08:00) instead of Z. The pretty style returns local HH:MM:SS. The hasTimestampPrefix regex is updated to accept both Z and +/-HH:MM offset suffixes. Closes #14699
This commit is contained in:
committed by
Peter Steinberger
parent
af172742a3
commit
468414cac4
@@ -86,7 +86,10 @@ describe("enableConsoleCapture", () => {
|
||||
console.warn("[EventQueue] Slow listener detected");
|
||||
expect(warn).toHaveBeenCalledTimes(1);
|
||||
const firstArg = String(warn.mock.calls[0]?.[0] ?? "");
|
||||
expect(firstArg.startsWith("2026-01-17T18:01:02.000Z [EventQueue]")).toBe(true);
|
||||
// Timestamp uses local time with timezone offset instead of UTC "Z" suffix
|
||||
expect(firstArg).toMatch(
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2} \[EventQueue\]/,
|
||||
);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
|
||||
39
src/logging/console-timestamp.test.ts
Normal file
39
src/logging/console-timestamp.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatConsoleTimestamp } from "./console.js";
|
||||
|
||||
describe("formatConsoleTimestamp", () => {
|
||||
it("pretty style returns local HH:MM:SS", () => {
|
||||
const result = formatConsoleTimestamp("pretty");
|
||||
expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/);
|
||||
// Verify it uses local time, not UTC
|
||||
const now = new Date();
|
||||
const expectedHour = String(now.getHours()).padStart(2, "0");
|
||||
expect(result.slice(0, 2)).toBe(expectedHour);
|
||||
});
|
||||
|
||||
it("compact style returns local ISO-like timestamp with timezone offset", () => {
|
||||
const result = formatConsoleTimestamp("compact");
|
||||
// Should match: YYYY-MM-DDTHH:MM:SS.mmm+HH:MM or -HH:MM
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/);
|
||||
// Should NOT end with Z (UTC indicator)
|
||||
expect(result).not.toMatch(/Z$/);
|
||||
});
|
||||
|
||||
it("json style returns local ISO-like timestamp with timezone offset", () => {
|
||||
const result = formatConsoleTimestamp("json");
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/);
|
||||
expect(result).not.toMatch(/Z$/);
|
||||
});
|
||||
|
||||
it("timestamp contains the correct local date components", () => {
|
||||
const before = new Date();
|
||||
const result = formatConsoleTimestamp("compact");
|
||||
const after = new Date();
|
||||
// The date portion should match the local date
|
||||
const datePart = result.slice(0, 10);
|
||||
const beforeDate = `${before.getFullYear()}-${String(before.getMonth() + 1).padStart(2, "0")}-${String(before.getDate()).padStart(2, "0")}`;
|
||||
const afterDate = `${after.getFullYear()}-${String(after.getMonth() + 1).padStart(2, "0")}-${String(after.getDate()).padStart(2, "0")}`;
|
||||
// Allow for date boundary crossing during test
|
||||
expect([beforeDate, afterDate]).toContain(datePart);
|
||||
});
|
||||
});
|
||||
@@ -135,16 +135,32 @@ function isEpipeError(err: unknown): boolean {
|
||||
return code === "EPIPE" || code === "EIO";
|
||||
}
|
||||
|
||||
function formatConsoleTimestamp(style: ConsoleStyle): string {
|
||||
const now = new Date().toISOString();
|
||||
export function formatConsoleTimestamp(style: ConsoleStyle): string {
|
||||
const now = new Date();
|
||||
if (style === "pretty") {
|
||||
return now.slice(11, 19);
|
||||
const h = String(now.getHours()).padStart(2, "0");
|
||||
const m = String(now.getMinutes()).padStart(2, "0");
|
||||
const s = String(now.getSeconds()).padStart(2, "0");
|
||||
return `${h}:${m}:${s}`;
|
||||
}
|
||||
return now;
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
const h = String(now.getHours()).padStart(2, "0");
|
||||
const m = String(now.getMinutes()).padStart(2, "0");
|
||||
const s = String(now.getSeconds()).padStart(2, "0");
|
||||
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
||||
const tzOffset = now.getTimezoneOffset();
|
||||
const tzSign = tzOffset <= 0 ? "+" : "-";
|
||||
const tzHours = String(Math.floor(Math.abs(tzOffset) / 60)).padStart(2, "0");
|
||||
const tzMinutes = String(Math.abs(tzOffset) % 60).padStart(2, "0");
|
||||
return `${year}-${month}-${day}T${h}:${m}:${s}.${ms}${tzSign}${tzHours}:${tzMinutes}`;
|
||||
}
|
||||
|
||||
function hasTimestampPrefix(value: string): boolean {
|
||||
return /^(?:\d{2}:\d{2}:\d{2}|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)/.test(value);
|
||||
return /^(?:\d{2}:\d{2}:\d{2}|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)/.test(
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
function isJsonPayload(value: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user