mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-13 10:43:43 +03:00
fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies (#10038)
* fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies Extracted from PR #9439 (agentic conversation tracking). Most of the original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB env var support, the estimateSizeFast() earlyExitAt parameterization) turned out to already be present on the current upstream/release/v3.8.50 tip -- confirmed via diff and by running check-env-doc-sync.test.ts / tests/unit/chatcore-log-truncation.test.ts against pristine upstream before making any changes here. Only two genuine gaps remained: 1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but undocumented in .env.example and docs/reference/ENVIRONMENT.md -- tests/unit/check-env-doc-sync.test.ts flags any env var read in code but missing from both doc files. Documented it (both required -- the same test enforces the pairing). 2. truncateForLog()'s summary only computed messageCount from obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses request (which uses input[], not messages[]) got summarized with no count at all, leaving the dashboard's "Full Conversation" panel nothing to base its "N messages not shown" placeholder on for any Responses-API conversation, even though the same summarization logic applies to it. Test plan: - TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test ("captures a message count for Responses API bodies too") confirmed failing against the pre-fix code, passing after. - tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no longer appears in codeMissingEnv (remaining drift in that test is pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS, COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS, TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine upstream/release/v3.8.50 checkout, base-red inherited: #9985). - tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing. - npx tsc --noEmit / npm run lint -- clean. ⚠️ base-red inherited: #9985 * docs(logging): consolidate CHAT_LOG_MAX_BODY_KB into a single entry per file The variable was already documented (with a stale src/lib/chatLogTruncation.ts reference in .env.example); keep the new richer entries next to the CHAT_LOG_* family and drop the old duplicates. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -1449,6 +1449,11 @@ APP_LOG_TO_FILE=true
|
||||
# CHAT_LOG_ARRAY_TAIL_ITEMS=128 # Number of array items retained from tail (default: 128)
|
||||
# CHAT_LOG_MAX_DEPTH=6 # Max nesting depth before truncation (default: 6)
|
||||
# CHAT_LOG_MAX_OBJECT_KEYS=80 # Max object keys retained (default: 80, 0 = no limit)
|
||||
# CHAT_LOG_MAX_BODY_KB=1024 # Whole request/response body size before it's replaced by a bare
|
||||
# {_truncated, messageCount, ...} summary instead of the full clone
|
||||
# (default: 1024 KB / 1MB). Raise this if the dashboard's "Full
|
||||
# Conversation" transcript panel shows a placeholder instead of the
|
||||
# actual messages for long agentic conversations.
|
||||
|
||||
# Maximum rows in the proxy_logs SQLite table.
|
||||
# Default: 100000
|
||||
@@ -2626,10 +2631,6 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# Used by: src/app/api/jobs/[id]/run-now/route.ts. Default: 30000 (30 seconds)
|
||||
# OMNIROUTE_RUNNOW_TIMEOUT_MS=30000
|
||||
|
||||
# Maximum request/response body size before chat-log summarization, in KiB.
|
||||
# Used by: src/lib/chatLogTruncation.ts. Default: 1024
|
||||
# CHAT_LOG_MAX_BODY_KB=1024
|
||||
|
||||
# Adobe Firefly browser renewal and durable session cache (enabled by default).
|
||||
# Used by: open-sse/services/adobeFireflySession.ts.
|
||||
# ADOBE_FIREFLY_BROWSER_REFRESH=1
|
||||
|
||||
@@ -753,6 +753,7 @@ The logging system writes to both stdout and rotated log files. All configuratio
|
||||
| `CHAT_LOG_ARRAY_TAIL_ITEMS` | `128` | Number of array items retained from the tail when truncating chat log payloads. |
|
||||
| `CHAT_LOG_MAX_DEPTH` | `6` | Max nesting depth before chat log payloads are truncated. |
|
||||
| `CHAT_LOG_MAX_OBJECT_KEYS` | `80` | Max object keys retained in chat log payloads (0 = unlimited). |
|
||||
| `CHAT_LOG_MAX_BODY_KB` | `1024` | Whole request/response body size (KB) before it's replaced by a bare summary instead of the full clone. Raise this if long agentic conversations show a placeholder instead of the real messages in the dashboard. |
|
||||
| `CHAT_DEBUG_FILE` | `false` | When true, `serializeArtifactForStorage` skips size-based truncation. Debug only. |
|
||||
|
||||
---
|
||||
@@ -1446,7 +1447,6 @@ These settings were introduced after the previous environment-contract snapshot.
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_TTL_MS` | `60000` (60 s) | `src/shared/middleware/chatBodyAdmission.ts` | Per-connection virtual admission lanes (#9654): idle-lane eviction TTL. |
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Per-connection virtual admission lanes (#9654): max concurrent sessions (lanes). |
|
||||
| `OMNIROUTE_RUNNOW_TIMEOUT_MS` | `30000` | `src/app/api/jobs/[id]/run-now/route.ts` | Bounds how long a run-now call waits for an in-flight job before starting the queued run. |
|
||||
| `CHAT_LOG_MAX_BODY_KB` | `1024` | `src/lib/logEnv.ts` | Maximum request or response body size before log summarization, in KiB. |
|
||||
| `ADOBE_FIREFLY_BROWSER_REFRESH` | enabled | `open-sse/services/adobeFireflySession.ts` | Keeps IMS and browser-risk state fresh through account-scoped Chrome CDP sessions; set `0` to disable. |
|
||||
| `ADOBE_FIREFLY_SESSION_DISK` | enabled | `open-sse/services/adobeFireflySession.ts` | Persists repaired Adobe sessions under `DATA_DIR`; set `0` for memory-only state. |
|
||||
| `ADOBE_FIREFLY_MIN_SUBMIT_GAP_MS` | `12000` | `open-sse/services/adobeFireflySession.ts` | Minimum spacing between Adobe Firefly generate submissions. |
|
||||
|
||||
@@ -60,9 +60,9 @@ export function cloneBoundedChatLogPayload(value: unknown, depth = 0): unknown {
|
||||
|
||||
/**
|
||||
* Truncate a large object for logging. If its JSON representation exceeds
|
||||
* the configured max body size (getChatLogMaxBodyBytes()), return a
|
||||
* lightweight summary instead of the full clone. This prevents
|
||||
* persistAttemptLogs from holding multi-MB references to translatedBody
|
||||
* getChatLogMaxBodyBytes() (default 1MB; CHAT_LOG_MAX_BODY_KB env override),
|
||||
* return a lightweight summary instead of the full clone. This prevents
|
||||
* persistAttemptLogs from holding unbounded references to translatedBody
|
||||
* across 17 call sites per request.
|
||||
*
|
||||
* When the summarized object carries a `tools` definition, re-attach it
|
||||
@@ -77,6 +77,9 @@ export function truncateForLog(value: unknown): Record<string, unknown> | null |
|
||||
if (value === null || value === undefined) return value as null | undefined;
|
||||
if (typeof value !== "object") return value as unknown as Record<string, unknown>;
|
||||
const maxBodyBytes = getChatLogMaxBodyBytes();
|
||||
// Pass maxBodyBytes as the early-exit point — otherwise estimateSizeFast's
|
||||
// own default 256KB early-exit caps what it can ever report, silently
|
||||
// making any configured threshold above 256KB unreachable (#trunc-limit-config).
|
||||
const estimatedSize = estimateSizeFast(value, maxBodyBytes);
|
||||
if (estimatedSize <= maxBodyBytes) return value as Record<string, unknown>;
|
||||
// Object is too large — return a summary instead of a deep clone
|
||||
@@ -88,6 +91,11 @@ export function truncateForLog(value: unknown): Record<string, unknown> | null |
|
||||
if (typeof obj.model === "string") summary.model = obj.model;
|
||||
if (typeof obj.provider === "string") summary.provider = obj.provider;
|
||||
if (Array.isArray(obj.messages)) summary.messageCount = obj.messages.length;
|
||||
// Responses API bodies use `input[]`, not `messages[]` (OpenAI-chat/Gemini-only
|
||||
// field name) — without this, a large /v1/responses request got summarized
|
||||
// with no count at all, leaving the dashboard's "Full Conversation" panel
|
||||
// nothing to base its "N messages not shown" placeholder on.
|
||||
else if (Array.isArray(obj.input)) summary.messageCount = obj.input.length;
|
||||
if (Array.isArray(obj.contents)) summary.contentCount = obj.contents.length;
|
||||
if (typeof obj.stream === "boolean") summary.stream = obj.stream;
|
||||
if (Array.isArray(obj.tools)) summary.tools = cloneBoundedChatLogPayload(obj.tools);
|
||||
|
||||
@@ -179,10 +179,17 @@ export function getChatLogMaxObjectKeys(): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Was a hardcoded/default 8KB — trivially exceeded by any real multi-turn
|
||||
* agentic conversation, meaning the dashboard's "Full Conversation" panel
|
||||
* could only ever show a placeholder instead of the actual messages for
|
||||
* nearly every logged row of any conversation with real substance.
|
||||
* Cap for a single logged request/response body before it gets replaced by a
|
||||
* bare {_truncated, _originalBytes, messageCount, ...} summary instead of the
|
||||
* full clone (open-sse/handlers/chatCore/logTruncation.ts::truncateForLog()).
|
||||
* Was a hardcoded 8KB — trivially exceeded by any real multi-turn agentic
|
||||
* conversation, which meant the dashboard's "Full Conversation" transcript
|
||||
* panel could only ever show a placeholder instead of the actual messages
|
||||
* for nearly every logged row. Bumped 128x (to 1MB) by default and exposed
|
||||
* as an operator override for anyone who needs it even larger (or smaller,
|
||||
* on a memory-constrained box) — see the same "protect memory across many
|
||||
* call sites per request" reasoning truncateForLog()'s own doc comment
|
||||
* explains for why some cap must still exist.
|
||||
*/
|
||||
export function getChatLogMaxBodyBytes(): number {
|
||||
return parsePositiveInt(process.env.CHAT_LOG_MAX_BODY_KB, 1024) * 1024;
|
||||
|
||||
@@ -134,7 +134,7 @@ test("truncateForLog summarizes oversized payloads instead of cloning", () => {
|
||||
provider: "openai",
|
||||
stream: true,
|
||||
// distinct object references so estimateSizeFast (WeakSet-dedup) counts each one
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(64) })),
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(500) })),
|
||||
contents: [{ a: 1 }],
|
||||
};
|
||||
const summary = truncateForLog(huge) as Record<string, unknown>;
|
||||
@@ -150,6 +150,22 @@ test("truncateForLog summarizes oversized payloads instead of cloning", () => {
|
||||
assert.notEqual(summary, huge);
|
||||
});
|
||||
|
||||
test("truncateForLog captures a message count for Responses API bodies too (input[], not messages[])", () => {
|
||||
// Live bug: a large /v1/responses request got summarized with NO count at
|
||||
// all (messages/contents are OpenAI-chat/Gemini-only field names), so the
|
||||
// "Full Conversation" dashboard panel had nothing to base its "N messages
|
||||
// not shown" placeholder on for any Responses-API conversation, even
|
||||
// though the exact same 8KB summarization applies to it.
|
||||
const huge = {
|
||||
model: "gpt-5",
|
||||
stream: true,
|
||||
input: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(500) })),
|
||||
};
|
||||
const summary = truncateForLog(huge) as Record<string, unknown>;
|
||||
assert.equal(summary._truncated, true);
|
||||
assert.equal(summary.messageCount, 50000);
|
||||
});
|
||||
|
||||
test("truncateForLog keeps a bounded `tools` field alive when the request is summarized", () => {
|
||||
// A request whose message history alone blows well past the 8KB summary
|
||||
// threshold, but which also carries `tools` — a field that used to be
|
||||
@@ -184,7 +200,7 @@ test("truncateForLog keeps a bounded `tools` field alive when the request is sum
|
||||
model: "gpt-4o",
|
||||
provider: "openai",
|
||||
stream: true,
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(64) })),
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(500) })),
|
||||
tools,
|
||||
};
|
||||
|
||||
@@ -216,7 +232,7 @@ test("truncateForLog bounds an oversized `tools` array to the configured tail-it
|
||||
}));
|
||||
const huge = {
|
||||
model: "gpt-4o",
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(64) })),
|
||||
messages: Array.from({ length: 50000 }, () => ({ role: "user", content: "x".repeat(500) })),
|
||||
tools: manyTools,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user