Skip to content

fix: improve moderation error logs - #1932

Merged
steebchen merged 3 commits into
mainfrom
content-filter-logs
Mar 30, 2026
Merged

steebchen merged 3 commits into
mainfrom
content-filter-logs

Conversation

@steebchen

@steebchen steebchen commented Mar 30, 2026 •

Copy link
Copy Markdown
Member

Summary

  • replace raw console error logging in the OpenAI content-filter helper with the shared structured logger
  • include nested error cause and error code details for moderation fetch failures so fetch failed logs show the real root cause
  • add focused unit coverage for fetch-failure and missing-credential logging behavior

Verification

  • pnpm vitest run apps/gateway/src/chat/tools/openai-content-filter.spec.ts --no-file-parallelism
  • pnpm build

Notes

  • pnpm format still fails in this workspace because of unrelated pre-existing lint errors outside this change, including in apps/worker/src/worker.ts

Summary by CodeRabbit

  • Tests

    • Added tests covering content-filter error scenarios (nested causes, non-Error rejections, circular causes, timeouts, and missing credentials) to validate structured error logging and fail-open moderation behavior.
  • Bug Fixes

    • Improved content-filter error handling and structured logging: logs include error name, message, derived cause details, and an error code; moderation now fails open with empty results and null upstream ID on errors.

Copilot AI review requested due to automatic review settings March 30, 2026 10:48
@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Replaced ad-hoc console error logging with structured logger.error in the OpenAI content-filter, added helpers to extract multi-level error causes and codes, updated catch paths to pass Error objects to the logger, and extended tests to assert structured error logging and fail-open results. (≤50 words)

Changes

Cohort / File(s) Summary
Content-filter implementation
apps/gateway/src/chat/tools/openai-content-filter.ts
Refactored logModerationError to accept an optional error and emit structured logger.error payloads. Added helpers/types (ErrorWithCode, getErrorCode, buildModerationErrorDetails) to traverse error causes (depth up to 5). Updated catch paths to pass the caught error instead of stringified messages.
Tests — content filter
apps/gateway/src/chat/tools/openai-content-filter.spec.ts
Added four tests that mock fetch failures and missing credentials to assert logger.error is called with structured payload fields (mode, ids, inputType, timeout, error, errorName, optional errorCause, errorCode) and that the function returns unflagged moderation results with empty results/responses and upstreamRequestId: null.
Tests — API logging spies
apps/gateway/src/api.spec.ts
Replaced console.error spies with logger.error spies and strengthened assertions to expect the Error object as a second argument where applicable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

auto-merge

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: improve moderation error logs' directly and concisely describes the main change: replacing console.error with structured logger for moderation errors.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch content-filter-logs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/gateway/src/chat/tools/openai-content-filter.ts (1)

285-312: Consider guarding against circular cause references.

The depth limit of 5 prevents runaway iteration, but a malformed error with error.cause === error (or an indirect cycle within 5 hops) would still loop. This is an edge case, but adding a Set to track visited errors would make it fully defensive.

♻️ Optional defensive fix
 function getErrorCode(error: unknown): string | undefined {
 	if (!(error instanceof Error)) {
 		return undefined;
 	}

 	const directCode =
 		typeof (error as ErrorWithCode).code === "string"
 			? (error as ErrorWithCode).code
 			: undefined;
 	if (directCode) {
 		return directCode;
 	}

+	const seen = new Set<unknown>([error]);
 	let current = (error as ErrorWithCode).cause;
 	for (let depth = 0; depth < 5; depth++) {
-		if (!(current instanceof Error)) {
+		if (!(current instanceof Error) || seen.has(current)) {
 			return undefined;
 		}
+		seen.add(current);

 		if (typeof (current as ErrorWithCode).code === "string") {
 			return (current as ErrorWithCode).code;
 		}

 		current = (current as ErrorWithCode).cause;
 	}

 	return undefined;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/tools/openai-content-filter.ts` around lines 285 - 312,
The getErrorCode function can loop on circular error.cause chains; update its
traversal in getErrorCode to track visited error objects (e.g., a Set<Error>)
and check this set before descending into (error as ErrorWithCode).cause so you
break/return undefined if you encounter an already-seen Error, while keeping the
existing shallow depth limit; reference the getErrorCode function, the
ErrorWithCode type and the use of .cause when adding the visited guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/gateway/src/chat/tools/openai-content-filter.ts`:
- Around line 285-312: The getErrorCode function can loop on circular
error.cause chains; update its traversal in getErrorCode to track visited error
objects (e.g., a Set<Error>) and check this set before descending into (error as
ErrorWithCode).cause so you break/return undefined if you encounter an
already-seen Error, while keeping the existing shallow depth limit; reference
the getErrorCode function, the ErrorWithCode type and the use of .cause when
adding the visited guard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e7d62ab5-921d-44ca-8784-cddacd702e64

📥 Commits

Reviewing files that changed from the base of the PR and between 0fd3289 and b61ed33.

📒 Files selected for processing (2)
  • apps/gateway/src/chat/tools/openai-content-filter.spec.ts
  • apps/gateway/src/chat/tools/openai-content-filter.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves observability for the gateway’s OpenAI moderation/content-filter helper by moving error logging to the shared structured logger and enriching logs with nested cause/code details, with unit tests covering key failure modes.

Changes:

  • Replaced raw console.error moderation error logging with @llmgateway/logger.
  • Added extraction of nested error cause and error code for moderation request failures.
  • Added unit tests for fetch-failure logging and missing-credential logging.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/gateway/src/chat/tools/openai-content-filter.ts Switches moderation error logging to structured logger and adds error detail extraction (cause/code).
apps/gateway/src/chat/tools/openai-content-filter.spec.ts Adds focused tests asserting structured logging for fetch failures and missing credentials.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +314 to +326
function buildModerationErrorDetails(
error: unknown,
): Record<string, string | undefined> {
if (!(error instanceof Error)) {
return {};
}

return {
error: error.message,
errorName: error.name,
errorCause: extractErrorCause(error),
errorCode: getErrorCode(error),
};

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildModerationErrorDetails drops all error info when the caught value is not an Error, which means logModerationError can emit a moderation error log without any error field (regression vs the previous String(error) fallback). Consider adding a fallback field (e.g., error: String(error) / errorType) for non-Error throwables so logs remain actionable.

Copilot uses AI. Check for mistakes.
Comment on lines +285 to +312
function getErrorCode(error: unknown): string | undefined {
if (!(error instanceof Error)) {
return undefined;
}

const directCode =
typeof (error as ErrorWithCode).code === "string"
? (error as ErrorWithCode).code
: undefined;
if (directCode) {
return directCode;
}

let current = (error as ErrorWithCode).cause;
for (let depth = 0; depth < 5; depth++) {
if (!(current instanceof Error)) {
return undefined;
}

if (typeof (current as ErrorWithCode).code === "string") {
return (current as ErrorWithCode).code;
}

current = (current as ErrorWithCode).cause;
}

return undefined;
}

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getErrorCode (and the ErrorWithCode shape) duplicates the same logic already present in normalize-streaming-error.ts. Consider extracting a shared helper (e.g., extract-error-code.ts) so the code/code-depth behavior stays consistent across the gateway and avoids future drift.

Copilot uses AI. Check for mistakes.
@steebchen
steebchen enabled auto-merge March 30, 2026 11:15
@steebchen
steebchen added this pull request to the merge queue Mar 30, 2026
@steebchen
steebchen removed this pull request from the merge queue due to a manual request Mar 30, 2026
@steebchen
steebchen enabled auto-merge March 30, 2026 11:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6ff373d72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

apiKeyId: context.apiKeyId,
...payload,
});
...buildModerationErrorDetails(error),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard error detail enrichment on defined throwable

logModerationError always spreads buildModerationErrorDetails(error) into the payload, even when callers pass no throwable (e.g., the non-OK and invalid-response branches in runOpenAIContentFilterRequest). In those paths this now logs error: "undefined" and errorName: "UndefinedThrownValue", which misclassifies normal HTTP/parse moderation failures as missing exceptions and can skew alerting or log-based analytics. Only attach derived error fields when an actual thrown value is provided.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/gateway/src/chat/tools/openai-content-filter.spec.ts (1)

682-683: Assert logger invocation before indexing mock.calls[0].

This will produce clearer failures and avoid relying on non-null assertion if the call unexpectedly doesn’t happen.

Suggested tweak
-		const [eventName, payload, loggedError] = loggerErrorSpy.mock.calls[0]!;
+		expect(loggerErrorSpy).toHaveBeenCalledTimes(1);
+		const [eventName, payload, loggedError] = loggerErrorSpy.mock.calls[0]!;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/tools/openai-content-filter.spec.ts` around lines 682 -
683, The test accesses loggerErrorSpy.mock.calls[0] with a non-null assertion
which can crash the test and produce unclear failures; before destructuring the
call, add an assertion that loggerErrorSpy was called (e.g.,
expect(loggerErrorSpy).toHaveBeenCalled() or toHaveBeenCalledTimes(1)) so the
test fails clearly if no call occurred, then destructure the first call into
[eventName, payload, loggedError] and assert eventName ===
"gateway_content_filter_error".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/gateway/src/chat/tools/openai-content-filter.spec.ts`:
- Around line 682-683: The test accesses loggerErrorSpy.mock.calls[0] with a
non-null assertion which can crash the test and produce unclear failures; before
destructuring the call, add an assertion that loggerErrorSpy was called (e.g.,
expect(loggerErrorSpy).toHaveBeenCalled() or toHaveBeenCalledTimes(1)) so the
test fails clearly if no call occurred, then destructure the first call into
[eventName, payload, loggedError] and assert eventName ===
"gateway_content_filter_error".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d90eee0c-2e50-4de7-95ec-efde1f872ece

📥 Commits

Reviewing files that changed from the base of the PR and between 2f0908b and b6ff373.

📒 Files selected for processing (2)
  • apps/gateway/src/chat/tools/openai-content-filter.spec.ts
  • apps/gateway/src/chat/tools/openai-content-filter.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/gateway/src/chat/tools/openai-content-filter.ts

@steebchen
steebchen added this pull request to the merge queue Mar 30, 2026
Merged via the queue into main with commit 46355b0 Mar 30, 2026
16 checks passed
@steebchen
steebchen deleted the content-filter-logs branch March 30, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants