惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Google DeepMind News
Google DeepMind News
MyScale Blog
MyScale Blog
A
About on SuperTechFans
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
Recent Announcements
Recent Announcements
D
DataBreaches.Net
B
Blog
博客园 - 【当耐特】
爱范儿
爱范儿
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
WordPress大学
WordPress大学
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
Microsoft Azure Blog
Microsoft Azure Blog
雷峰网
雷峰网
量子位
G
Google Developers Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
feat(logging): add file log correlation fields · openclaw...
vincentkoc · 2026-04-27 · via Recent Commits to openclaw:main

@@ -1,4 +1,5 @@

11

import fs from "node:fs";

2+

import os from "node:os";

23

import path from "node:path";

34

import { Logger as TsLogger } from "tslog";

45

import type { OpenClawConfig } from "../config/types.js";

@@ -79,7 +80,10 @@ const MAX_DIAGNOSTIC_LOG_MESSAGE_CHARS = 4 * 1024;

7980

const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_COUNT = 32;

8081

const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_VALUE_CHARS = 2 * 1024;

8182

const MAX_DIAGNOSTIC_LOG_NAME_CHARS = 120;

83+

const MAX_FILE_LOG_MESSAGE_CHARS = 4 * 1024;

84+

const MAX_FILE_LOG_CONTEXT_VALUE_CHARS = 512;

8285

const DIAGNOSTIC_LOG_ATTRIBUTE_KEY_RE = /^[A-Za-z0-9_.:-]{1,64}$/u;

86+

const HOSTNAME = os.hostname() || "unknown";

83878488

type 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+213286

function extractLogBindingPrefix(numericArgs: unknown[]): {

214287

bindings?: Record<string, unknown>;

215288

args: 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+268360

function buildDiagnosticLogRecord(logObj: TsLogRecord) {

269361

const meta = logObj._meta as

270362

| {

@@ -447,7 +539,10 @@ function buildLogger(settings: ResolvedSettings): TsLogger<LogObj> {

447539

}

448540

const time = formatTimestamp(logObj.date ?? new Date(), { style: "long" });

449541

const 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+

);

451546

const payload = `${line}\n`;

452547

const payloadBytes = Buffer.byteLength(payload, "utf8");

453548

const nextBytes = currentFileBytes + payloadBytes;