Skip to content

fix(sls): add structured flush failure diagnostics#130

Open
Monsooooon wants to merge 2 commits into
mainfrom
multica/loo-30-cp1
Open

fix(sls): add structured flush failure diagnostics#130
Monsooooon wants to merge 2 commits into
mainfrom
multica/loo-30-cp1

Conversation

@Monsooooon

Copy link
Copy Markdown
Collaborator

背景

GitHub issue #123 里 Sentinel 观察到 FLUSH_SEND_ALARM 持续约 25 分钟,但现有告警只提供泛化错误文本,缺少可直接定位 SLS 发送失败原因的结构化字段。

核心改动

  • 为 AK / webtracking 两条 SLS 发送路径增加失败分类:failure_classstatus_coderetryablereason
  • FLUSH_SEND_ALARMFLUSH_QUOTA_ALARM 保持既有 alarm_type,只追加安全诊断上下文。
  • failed-log 和 L2 flusher metrics 增加 last failure / consecutive failure 信息。
  • endpoint_host 仅保留 hostname;reason 做 URL、AK/SK、token 等去敏并限制长度。
  • 更新 SLS 诊断文档与中英文输出文档。

修改文件

文件 说明
src/flushers/sls-transport.ts SLS 失败分类、host 提取、reason 去敏
src/flushers/sls-flusher.ts AK/webtracking alarm、failed-log、counter 诊断字段
src/metrics/alarm-manager.ts alarm context additive 字段与按 failure class/status 聚合
src/metrics/metrics-collector.ts L2 flusher metrics 新增 failure 字段
docs/sls-output.md / docs/zh-CN/sls-output.md / assets/skills/references/sls-diagnostics.md 诊断字段说明与排查建议
tests/unit/... 覆盖分类、去敏、alarm、metrics、failed-log 行为

验证结果

  • git diff --check: PASS
  • 定向单测: npx vitest run tests/unit/flushers/sls-transport.test.ts tests/unit/flushers/sls-flusher.test.ts tests/unit/flushers/sls-flusher.dual-write.test.ts tests/unit/metrics/alarm-manager.test.ts tests/unit/metrics/metrics-collector.test.ts tests/unit/metrics/metrics-writer.test.ts -> 6 files / 107 tests PASS
  • npm run typecheck: PASS
  • npm test: 143 files PASS, 1567 tests PASS, 2 skipped
  • npm run build: PASS
  • Codex E2E: tester 使用真实 codex exec 触发 3+ STEP / 2+ TOOL,mock SLS 403 / 429 / 500 均命中,failed-log、alarm、metrics 均包含新增字段;429 保留 FLUSH_QUOTA_ALARM
  • Trace validation: 当前分支与 base main 均缺 docs/trace-validation-rules.json,所以默认 node scripts/validate-trace.mjs --latest --format text 会因 ENOENT 失败;GitHub Actions PR CI 不调用该脚本,不影响线上 CI。使用脚本支持的显式历史规则文件 --rules <(git show aede3d5:docs/trace-validation-rules.json) 校验同一条真实 E2E trace 为 1 traces, 16 spans, 0 errors, 170 warnings, 0 skipped

准出判断

  • 本 PR 不改变 alarm_type、重试次数、batch 大小、失败落盘路径或 endpoint 隔离语义。
  • 新字段均为 additive,且单测/E2E 覆盖 403、429、500 与去敏检查。
  • validate-trace 默认规则文件缺失是 base main 已存在的验证工具资料缺失,不属于本 PR 引入,也不会触发 PR CI;本轮作为非阻塞风险记录。

Classify SLS send failures across AK and webtracking paths, attach safe diagnostic context to alarms, failed logs, and flusher metrics, and document the new fields with focused unit coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@ralf0131 ralf0131 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

PR adds structured failure diagnostics to SLS flusher: normalized failure_class classification, HTTP status code extraction, redacted reason summary, and endpoint_host (hostname-only) to both FLUSH_SEND_ALARM and failed-log entries. All changes are additive — optional fields in interfaces, backward-compatible alarm aggregation when diagnostic fields are absent.

Key Strengths

  • Security: sanitizeFailureReason properly redacts AccessKeyId/accessKeySecret, Bearer/Basic auth, and full URLs; truncated to 240 chars. Tests explicitly verify no sensitive data leaks into alarms or failed-logs.
  • Backward compat: Legacy alarm dedup key preserved when failure_class/status_code absent; isRetryable() behavior unchanged.
  • Test coverage: Comprehensive unit tests for classifySlsFailure, extractEndpointHost, sanitizeFailureReason, alarm splitting, metrics, and flusher integration — including AK and webtracking paths.
  • Documentation: Both EN and zh-CN docs updated with diagnostic field reference and troubleshooting table.

Observations

  • [Info] isRetryableFailure ignores _failureClass parameter and falls back to status-code/text matching — intentional for backward compat, consistent with prior behavior.
  • [Info] Alarm dedup key now includes failure_class + status_code, meaning different failure classes for the same endpoint produce separate alarms. This is the correct design for diagnostic clarity.

LGTM. Clean, well-tested, additive change with strong security redaction.


Automated review by github-manager-bot

Extract status and errno details from nested fetch causes so refused and timeout transport errors use network failure classes with retryable diagnostics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@ralf0131 ralf0131 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

The new commit (544c50b) enhances SLS transport failure classification to handle nested fetch errors. Recursive cause chain traversal, AggregateError (errors[]) support, and errno-based classification are all well-implemented with proper circular-reference protection (seen Set) and depth limits (MAX_ERROR_CAUSE_DEPTH=6). New tests cover nested TypeError classification, deep cause chains, and abort errors. No issues found.

Findings

  • [Info] The hardcoded errno values (60, 110, 54, 61, 104, 111) alongside osConstants.errno.* correctly handle cross-platform differences (macOS vs Linux). Consider adding a brief comment documenting which platform each numeric value corresponds to, for future maintainers.

Automated review by github-manager-bot

@ralf0131

Copy link
Copy Markdown
Collaborator

⚠️ Merge conflict detected

This PR has conflicts with the main branch and cannot be merged. Please rebase or merge main into your branch and resolve the conflicts:

git fetch origin
git checkout multica/loo-30-cp1
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease

This is a one-time reminder. Feel free to @mention me for a re-review after conflicts are resolved.


Automated notification by github-manager-bot

return 'unknown';
}

function isRetryableFailure(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[High] isRetryableFailure 文本兜底判断与 classifyByText 不对齐

影响: classifyByText"serviceunavailable""internal server error"(含空格)分类为 server_error,但 isRetryableFailure 的文本兜底列表不包含这些模式。无 status code 的服务端错误会被分类为 server_errorretryable=false,跳过重试直接失败。

建议:isRetryableFailure 中基于 failureClass 补充显式判定:

if (
  failureClass === 'server_error' ||
  failureClass === 'quota_throttle'
) return true;

这让 retryable 判定直接引用 failureClass 而非重复文本匹配,消除分类层与重试层之间的不一致。

);
}
await this.persistFailedLogs(endpoint, logGroup, lastErr);
await this.persistFailedLogs(endpoint, logGroup, diagnostics);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Medium] persistFailedLogs 未被 try-catch 保护,磁盘异常导致计数错误

影响: flushViaAk/postWebtracking 内部 catch 了 SLS 错误并返回 FlushResult,但 persistFailedLogs 若抛异常(如 ENOSPC),整个 promise reject。.catch() handler 把全部 logs.length 计为 outFailed,即使 webtracking 的前几个 chunk 已成功发送。且 classifySlsFailure(err) 将文件系统错误误分类为 SLS 故障。

建议:

try {
  await this.persistFailedLogs(endpoint, logGroup, diagnostics);
} catch (e) {
  logger.warn('Failed to persist failed logs', { error: String(e) });
}
return { sentEntries: 0, failedEntries: logs.length, lastFailure: diagnostics };

同样处理 webtracking 路径(line 425)。

context?.endpoint_name ?? '',
context?.failure_class ?? '',
context?.status_code ?? '',
].join('_');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Medium] 告警 dedup key 含 status_code 可能导致告警碎片化

影响: 旧 key 为 ${type}_${input_name}_${endpoint_name},同一 endpoint 的失败合并为一条告警。新 key 加入 failure_class + status_code 后,若负载均衡器交替返回 502/503/504,每种 status_code 各产生一条独立告警条目。原本 count=3 的单条告警分裂为 3 条 count=1,下游告警规则若基于 alarm_count 做阈值判断可能漏报。

建议: 考虑从 dedup key 中移除 status_code,仅保留 failure_classstatus_code 仍作为 context 字段传递:

const key = [type, context?.input_name ?? '', context?.endpoint_name ?? '', context?.failure_class ?? ''].join('_');

lower.includes('econnrefused') ||
lower.includes('econnreset') ||
lower.includes('socket hang up') ||
lower.includes('network') ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Medium] classifyByText"network" 匹配过宽

影响: lower.includes('network') 会匹配任何包含 "network" 的错误消息(如 "network policy violation"),误分类为 network_refused。老代码也有宽匹配,但新代码将影响从"仅影响重试"扩大到"影响 failure_class 标签、告警、failed-log、metrics"。

建议: 收紧为 lower.includes('networkerror') || lower.includes('network error') || lower.includes('fetch failed')

);
}

function textHasErrno(text: string, errnoValues: Set<string>): boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Medium] textHasErrno 每次调用动态创建 RegExp 对象

影响: 每次调用在循环内为每个 errno 值创建新 RegExp(最多 12 个)。classifyByText 中调用两次,每次错误分类最多创建 24 个 RegExp 对象。频率低但易优化。

建议: 预编译为模块级 Map<string, RegExp>,或合并为单个 alternation regex per set(如 /\berrno[:=]\s*(?:60|-60|110|-110)\b/)。

@linrunqi08

Copy link
Copy Markdown
Collaborator

🔍 Code Review Summary

Severity Count
Critical 0
High 1
Medium 5
Low 4

Lifecycle Verdict

Check Result
资源释放 FAIL
死锁/卡死风险 PASS
状态恢复正确性 PASS

资源释放 FAIL: shutdown() 不等待 setInterval/enqueue 已触发的 in-flight flush() 完成,已从队列取出但未发送完的数据可能在进程退出时丢失。此为 PR 放大的已有问题。

总体结论

PR 整体架构扎实,additive 改动向后兼容,安全去敏设计(4 层 regex + 白名单提取 + 240 char 截断)有效防止凭据泄露。不阻断合入,但建议合入前修复以下 2 项:

  1. [High] F1: isRetryableFailureclassifyByText 不对齐 — server_error 类中 "serviceunavailable" 和含空格的 "internal server error" 被分类但不被重试。建议在 isRetryableFailure 中对 server_error/quota_throttle failureClass 补充显式 return true
  2. [Medium] F2: persistFailedLogs 未被 try-catch 保护 — 磁盘异常导致 promise reject,.catch() 将已成功发送的条目误计为 failed。

其余 Medium 项(告警 dedup key 碎片化、"network" 匹配过宽、shutdown 不等待 in-flight flush、textHasErrno 动态 RegExp)可根据优先级安排修复。

Highlights(正向实践)

  • sanitizeFailureReason 的 4 层去敏 + ERROR_SUMMARY_KEYS 白名单提取,形成有效纵深防御
  • isRetryable() 重构向后兼容且修复了 AK SDK 错误不被重试的老问题
  • 分类器支持 cause 链遍历、AggregateError、循环引用检测
  • 6 个测试文件 +342 行覆盖 AK/webtracking 双路径
  • EN/zh-CN 文档内容一致

评审报告详见: code-review/pr-130/final-report.md
Generated by LoongSuite-Pilot Code Review Agent

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.

3 participants