mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-21 12:04:59 +00:00
logging: use local time (with tz offset) everywhere instead of UTC
This commit is contained in:
@@ -2,7 +2,7 @@ import type { Command } from "commander";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||
import { parseLogLine } from "../logging/parse-log-line.js";
|
||||
import { formatLocalIsoWithOffset } from "../logging/timestamps.js";
|
||||
import { formatLocalIso } from "../logging/timestamp.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { clearActiveProgressLine } from "../terminal/progress-line.js";
|
||||
import { createSafeStreamWriter } from "../terminal/stream-writer.js";
|
||||
@@ -73,17 +73,13 @@ export function formatLogTimestamp(
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let timeString: string;
|
||||
if (localTime) {
|
||||
timeString = formatLocalIsoWithOffset(parsed);
|
||||
} else {
|
||||
timeString = parsed.toISOString();
|
||||
}
|
||||
if (mode === "pretty") {
|
||||
return timeString.slice(11, 19);
|
||||
const h = String(parsed.getHours()).padStart(2, "0");
|
||||
const m = String(parsed.getMinutes()).padStart(2, "0");
|
||||
const s = String(parsed.getSeconds()).padStart(2, "0");
|
||||
return `${h}:${m}:${s}`;
|
||||
}
|
||||
return timeString;
|
||||
return formatLocalIso(parsed);
|
||||
}
|
||||
|
||||
function formatLogLine(
|
||||
|
||||
@@ -28,6 +28,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { HookHandler } from "../../hooks.js";
|
||||
import { resolveStateDir } from "../../../config/paths.js";
|
||||
import { formatLocalIso } from "../../../logging/timestamp.js";
|
||||
|
||||
/**
|
||||
* Log all command events to a file
|
||||
@@ -48,7 +49,7 @@ const logCommand: HookHandler = async (event) => {
|
||||
const logFile = path.join(logDir, "commands.log");
|
||||
const logLine =
|
||||
JSON.stringify({
|
||||
timestamp: event.timestamp.toISOString(),
|
||||
timestamp: formatLocalIso(event.timestamp),
|
||||
action: event.action,
|
||||
sessionKey: event.sessionKey,
|
||||
senderId: event.context.senderId ?? "unknown",
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { HookHandler } from "../../hooks.js";
|
||||
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
|
||||
import { resolveStateDir } from "../../../config/paths.js";
|
||||
import { createSubsystemLogger } from "../../../logging/subsystem.js";
|
||||
import { localDateStr, localTimeStr, tzOffsetLabel } from "../../../logging/timestamp.js";
|
||||
import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
|
||||
import { hasInterSessionUserProvenance } from "../../../sessions/input-provenance.js";
|
||||
import { resolveHookConfig } from "../../config.js";
|
||||
@@ -88,14 +89,15 @@ async function saveToLanceDB(params: {
|
||||
}
|
||||
|
||||
// Format memory text with metadata and truncated content
|
||||
const dateStr = timestamp.toISOString().split("T")[0];
|
||||
const timeStr = timestamp.toISOString().split("T")[1].split(".")[0];
|
||||
const dateStr = localDateStr(timestamp);
|
||||
const timeStr = localTimeStr(timestamp);
|
||||
const tz = tzOffsetLabel(timestamp);
|
||||
const truncatedContent = sessionContent.slice(0, 2000);
|
||||
const wasTruncated = sessionContent.length > 2000;
|
||||
|
||||
const memoryText = [
|
||||
`Session: ${slug}`,
|
||||
`Date: ${dateStr} ${timeStr} UTC`,
|
||||
`Date: ${dateStr} ${timeStr} ${tz}`,
|
||||
`Session Key: ${sessionKey}`,
|
||||
"",
|
||||
truncatedContent,
|
||||
@@ -149,7 +151,7 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
|
||||
// Get today's date for filename
|
||||
const now = new Date(event.timestamp);
|
||||
const dateStr = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
const dateStr = localDateStr(now);
|
||||
|
||||
// Generate descriptive slug from session using LLM
|
||||
const sessionEntry = (context.previousSessionEntry || context.sessionEntry || {}) as Record<
|
||||
@@ -206,7 +208,7 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
|
||||
// If no slug, use timestamp
|
||||
if (!slug) {
|
||||
const timeSlug = now.toISOString().split("T")[1].split(".")[0].replace(/:/g, "");
|
||||
const timeSlug = localTimeStr(now).replace(/:/g, "");
|
||||
slug = timeSlug.slice(0, 4); // HHMM
|
||||
log.debug("Using fallback timestamp slug", { slug });
|
||||
}
|
||||
@@ -243,8 +245,8 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
path: memoryFilePath.replace(os.homedir(), "~"),
|
||||
});
|
||||
|
||||
// Format time as HH:MM:SS UTC
|
||||
const timeStr = now.toISOString().split("T")[1].split(".")[0];
|
||||
const timeStr = localTimeStr(now);
|
||||
const tz = tzOffsetLabel(now);
|
||||
|
||||
// Extract context details
|
||||
const sessionId = (sessionEntry.sessionId as string) || "unknown";
|
||||
@@ -252,7 +254,7 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
|
||||
// Build Markdown entry
|
||||
const entryParts = [
|
||||
`# Session: ${dateStr} ${timeStr} UTC`,
|
||||
`# Session: ${dateStr} ${timeStr} ${tz}`,
|
||||
"",
|
||||
`- **Session Key**: ${event.sessionKey}`,
|
||||
`- **Session ID**: ${sessionId}`,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { readLoggingConfig } from "./config.js";
|
||||
import { type LogLevel, normalizeLogLevel } from "./levels.js";
|
||||
import { getLogger, type LoggerSettings } from "./logger.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import { formatLocalIsoWithOffset } from "./timestamps.js";
|
||||
import { formatLocalIso } from "./timestamp.js";
|
||||
|
||||
export type ConsoleStyle = "pretty" | "compact" | "json";
|
||||
type ConsoleSettings = {
|
||||
@@ -151,14 +151,14 @@ function isEpipeError(err: unknown): boolean {
|
||||
}
|
||||
|
||||
export function formatConsoleTimestamp(style: ConsoleStyle): string {
|
||||
const now = new Date();
|
||||
if (style === "pretty") {
|
||||
const now = new Date();
|
||||
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 formatLocalIsoWithOffset(now);
|
||||
return formatLocalIso();
|
||||
}
|
||||
|
||||
function hasTimestampPrefix(value: string): boolean {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
import { readLoggingConfig } from "./config.js";
|
||||
import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import { formatLocalIso } from "./timestamp.js";
|
||||
|
||||
// When running under vitest, isolate logs to avoid polluting production logs.
|
||||
function getDefaultLogDir(): string {
|
||||
@@ -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 = formatLocalIso(logObj.date ?? new Date());
|
||||
const line = JSON.stringify({ ...logObj, time });
|
||||
fs.appendFileSync(settings.file, `${line}\n`, { encoding: "utf8" });
|
||||
} catch {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getConsoleSettings, shouldLogSubsystemToConsole } from "./console.js";
|
||||
import { type LogLevel, levelToMinLevel } from "./levels.js";
|
||||
import { getChildLogger, isFileLogLevelEnabled } from "./logger.js";
|
||||
import { loggingState } from "./state.js";
|
||||
import { formatLocalIso } from "./timestamp.js";
|
||||
|
||||
type LogObj = { date?: Date } & Record<string, unknown>;
|
||||
|
||||
@@ -155,7 +156,7 @@ function formatConsoleLine(opts: {
|
||||
opts.style === "json" ? opts.subsystem : formatSubsystemForConsole(opts.subsystem);
|
||||
if (opts.style === "json") {
|
||||
return JSON.stringify({
|
||||
time: new Date().toISOString(),
|
||||
time: formatLocalIso(),
|
||||
level: opts.level,
|
||||
subsystem: displaySubsystem,
|
||||
message: opts.message,
|
||||
|
||||
29
src/logging/timestamp.ts
Normal file
29
src/logging/timestamp.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/** Format a Date as local ISO-8601 with milliseconds and timezone offset. */
|
||||
export function formatLocalIso(date?: Date): string {
|
||||
const d = date ?? new Date();
|
||||
const p = (n: number, len = 2) => String(n).padStart(len, "0");
|
||||
const off = -d.getTimezoneOffset();
|
||||
const sign = off >= 0 ? "+" : "-";
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p(d.getMilliseconds(), 3)}${sign}${p(Math.floor(Math.abs(off) / 60))}:${p(Math.abs(off) % 60)}`;
|
||||
}
|
||||
|
||||
/** Extract local date portion (YYYY-MM-DD) from a Date. */
|
||||
export function localDateStr(date: Date): string {
|
||||
const p = (n: number) => String(n).padStart(2, "0");
|
||||
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`;
|
||||
}
|
||||
|
||||
/** Extract local time portion (HH:MM:SS) from a Date. */
|
||||
export function localTimeStr(date: Date): string {
|
||||
const p = (n: number) => String(n).padStart(2, "0");
|
||||
return `${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
/** Get timezone offset label like "+08:00" from a Date. */
|
||||
export function tzOffsetLabel(date?: Date): string {
|
||||
const d = date ?? new Date();
|
||||
const p = (n: number) => String(n).padStart(2, "0");
|
||||
const off = -d.getTimezoneOffset();
|
||||
const sign = off >= 0 ? "+" : "-";
|
||||
return `${sign}${p(Math.floor(Math.abs(off) / 60))}:${p(Math.abs(off) % 60)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user