fix(logging): log timestamps use local time instead of UTC (#28434)
* fix(logging): log timestamps use local time instead of UTC Problem: Log timestamps used UTC, but docs say they should use host local timezone * test(logging): add test for logger timestamp format Verify logger uses local time (not UTC) in file logs * changelog: note logger timestamp local-time fix --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Zalo/Pairing auth tests: add webhook regression coverage asserting DM pairing-store reads/writes remain account-scoped, preventing cross-account authorization bleed in multi-account setups. (#26121) Thanks @bmendonca3.
|
||||
- Logging: use local time for logged timestamps instead of UTC, aligning log output with documented local timezone behavior and avoiding confusion during local diagnostics. (#28434) Thanks @liuy.
|
||||
- Zalouser/Pairing auth tests: add account-scoped DM pairing-store regression coverage (`monitor.account-scope.test.ts`) to prevent cross-account allowlist bleed in multi-account setups. (#26672) Thanks @bmendonca3.
|
||||
- Security/Web tools SSRF guard: keep DNS pinning for untrusted `web_fetch` and citation-redirect URL checks when proxy env vars are set, and require explicit dangerous opt-in before env-proxy routing can bypass pinned dispatch for trusted/operator-controlled endpoints. Thanks @tdjackey for reporting.
|
||||
- Gateway/Security canonicalization hardening: decode plugin route path variants to canonical fixpoint (with bounded depth), fail closed on canonicalization anomalies, and enforce gateway auth for deeply encoded `/api/channels/*` variants to prevent alternate-path auth bypass through plugin handlers. Thanks @tdjackey for reporting.
|
||||
|
||||
44
src/logging/logger-timestamp.test.ts
Normal file
44
src/logging/logger-timestamp.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { getLogger, resetLogger, setLoggerOverride } from "../logging.js";
|
||||
|
||||
describe("logger timestamp format", () => {
|
||||
let logPath = "";
|
||||
|
||||
beforeEach(() => {
|
||||
logPath = path.join(os.tmpdir(), `openclaw-log-ts-${crypto.randomUUID()}.log`);
|
||||
resetLogger();
|
||||
setLoggerOverride(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetLogger();
|
||||
setLoggerOverride(null);
|
||||
try {
|
||||
fs.rmSync(logPath, { force: true });
|
||||
} catch {
|
||||
// ignore cleanup errors
|
||||
}
|
||||
});
|
||||
|
||||
it("uses local time format in file logs (not UTC)", () => {
|
||||
setLoggerOverride({ level: "info", file: logPath });
|
||||
const logger = getLogger();
|
||||
|
||||
// Write a log entry
|
||||
logger.info("test-timestamp-format");
|
||||
|
||||
// Read the log file
|
||||
const content = fs.readFileSync(logPath, "utf8");
|
||||
const lines = content.trim().split("\n");
|
||||
const lastLine = JSON.parse(lines[lines.length - 1]);
|
||||
|
||||
// Should use local time format like "2026-02-27T15:04:00.000+08:00"
|
||||
// NOT UTC format like "2026-02-27T07:04:00.000Z"
|
||||
expect(lastLine.time).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/);
|
||||
expect(lastLine.time).not.toMatch(/Z$/);
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { resolveEnvLogLevelOverride } from "./env-log-level.js";
|
||||
import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js";
|
||||
import { resolveNodeRequireFromMeta } from "./node-require.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import { formatLocalIsoWithOffset } from "./timestamps.js";
|
||||
|
||||
export const DEFAULT_LOG_DIR = resolvePreferredOpenClawTmpDir();
|
||||
export const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "openclaw.log"); // legacy single-file path
|
||||
@@ -113,7 +114,7 @@ function buildLogger(settings: ResolvedSettings): TsLogger<LogObj> {
|
||||
|
||||
logger.attachTransport((logObj: LogObj) => {
|
||||
try {
|
||||
const time = logObj.date?.toISOString?.() ?? new Date().toISOString();
|
||||
const time = formatLocalIsoWithOffset(logObj.date ?? new Date());
|
||||
const line = JSON.stringify({ ...logObj, time });
|
||||
const payload = `${line}\n`;
|
||||
const payloadBytes = Buffer.byteLength(payload, "utf8");
|
||||
@@ -122,7 +123,7 @@ function buildLogger(settings: ResolvedSettings): TsLogger<LogObj> {
|
||||
if (!warnedAboutSizeCap) {
|
||||
warnedAboutSizeCap = true;
|
||||
const warningLine = JSON.stringify({
|
||||
time: new Date().toISOString(),
|
||||
time: formatLocalIsoWithOffset(new Date()),
|
||||
level: "warn",
|
||||
subsystem: "logging",
|
||||
message: `log file size cap reached; suppressing writes file=${settings.file} maxFileBytes=${settings.maxFileBytes}`,
|
||||
|
||||
Reference in New Issue
Block a user