

























@@ -1,4 +1,5 @@
11import fs from "node:fs";
2+import os from "node:os";
23import path from "node:path";
34import { Logger as TsLogger } from "tslog";
45import type { OpenClawConfig } from "../config/types.js";
@@ -79,7 +80,10 @@ const MAX_DIAGNOSTIC_LOG_MESSAGE_CHARS = 4 * 1024;
7980const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_COUNT = 32;
8081const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_VALUE_CHARS = 2 * 1024;
8182const MAX_DIAGNOSTIC_LOG_NAME_CHARS = 120;
83+const MAX_FILE_LOG_MESSAGE_CHARS = 4 * 1024;
84+const MAX_FILE_LOG_CONTEXT_VALUE_CHARS = 512;
8285const DIAGNOSTIC_LOG_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,64}$/u;
86+const HOSTNAME = os.hostname() || "unknown";
83878488type DiagnosticLogAttributes = Record<string, string | number | boolean>;
8589@@ -210,6 +214,75 @@ function getSortedNumericLogArgs(logObj: TsLogRecord): unknown[] {
210214.map(([, value]) => value);
211215}
212216217+function clampFileLogText(value: string, maxChars: number): string {
218+return value.length > maxChars ? `${value.slice(0, maxChars)}...(truncated)` : value;
219+}
220+221+function normalizeFileLogContextValue(value: unknown): string | undefined {
222+if (typeof value === "string") {
223+const normalized = value.trim();
224+return normalized ? clampFileLogText(normalized, MAX_FILE_LOG_CONTEXT_VALUE_CHARS) : undefined;
225+}
226+if (typeof value === "number" && Number.isFinite(value)) {
227+return String(value);
228+}
229+if (typeof value === "boolean") {
230+return String(value);
231+}
232+return undefined;
233+}
234+235+function readFirstContextString(
236+sources: Array<Record<string, unknown> | undefined>,
237+keys: readonly string[],
238+): string | undefined {
239+for (const source of sources) {
240+if (!source) {
241+continue;
242+}
243+for (const key of keys) {
244+const value = normalizeFileLogContextValue(source[key]);
245+if (value) {
246+return value;
247+}
248+}
249+}
250+return undefined;
251+}
252+253+function stringifyFileLogMessagePart(value: unknown): string | undefined {
254+if (typeof value === "string") {
255+return value;
256+}
257+if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
258+return String(value);
259+}
260+if (value instanceof Error) {
261+return value.message || value.name;
262+}
263+if (isPlainLogRecordObject(value) && typeof value.message === "string") {
264+return value.message;
265+}
266+if (value === null || value === undefined) {
267+return undefined;
268+}
269+try {
270+return JSON.stringify(value);
271+} catch {
272+return String(value);
273+}
274+}
275+276+function buildFileLogMessage(numericArgs: readonly unknown[]): string | undefined {
277+const parts = numericArgs
278+.map(stringifyFileLogMessagePart)
279+.filter((part): part is string => Boolean(part && part.trim()));
280+if (parts.length === 0) {
281+return undefined;
282+}
283+return clampFileLogText(parts.join(" "), MAX_FILE_LOG_MESSAGE_CHARS);
284+}
285+213286function extractLogBindingPrefix(numericArgs: unknown[]): {
214287bindings?: Record<string, unknown>;
215288args: unknown[];
@@ -265,6 +338,25 @@ function buildTraceFileLogFields(logObj: TsLogRecord): Record<string, string> |
265338};
266339}
267340341+function buildStructuredFileLogFields(logObj: TsLogRecord): Record<string, string> {
342+const { bindings, args } = extractLogBindingPrefix(getSortedNumericLogArgs(logObj));
343+const structuredArg = isPlainLogRecordObject(args[0]) ? args[0] : undefined;
344+const sources = [structuredArg, bindings, logObj];
345+const messageArgs =
346+structuredArg && typeof structuredArg.message !== "string" ? args.slice(1) : args;
347+const message = buildFileLogMessage(messageArgs);
348+const agentId = readFirstContextString(sources, ["agent_id", "agentId"]);
349+const sessionId = readFirstContextString(sources, ["session_id", "sessionId", "sessionKey"]);
350+const channel = readFirstContextString(sources, ["channel", "messageProvider"]);
351+return {
352+hostname: HOSTNAME,
353+ ...(message ? { message } : {}),
354+ ...(agentId ? { agent_id: agentId } : {}),
355+ ...(sessionId ? { session_id: sessionId } : {}),
356+ ...(channel ? { channel } : {}),
357+};
358+}
359+268360function buildDiagnosticLogRecord(logObj: TsLogRecord) {
269361const meta = logObj._meta as
270362| {
@@ -447,7 +539,10 @@ function buildLogger(settings: ResolvedSettings): TsLogger<LogObj> {
447539}
448540const time = formatTimestamp(logObj.date ?? new Date(), { style: "long" });
449541const traceFields = buildTraceFileLogFields(logObj as TsLogRecord);
450-const line = redactSensitiveText(JSON.stringify({ ...logObj, time, ...traceFields }));
542+const structuredFields = buildStructuredFileLogFields(logObj as TsLogRecord);
543+const line = redactSensitiveText(
544+JSON.stringify({ ...logObj, time, ...structuredFields, ...traceFields }),
545+);
451546const payload = `${line}\n`;
452547const payloadBytes = Buffer.byteLength(payload, "utf8");
453548const nextBytes = currentFileBytes + payloadBytes;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。