Skip to content

Commit 1195185

Browse files
feat: 更新 sentry 错误上报
1 parent 1f0a2e4 commit 1195185

11 files changed

Lines changed: 493 additions & 2 deletions

File tree

DEV-LOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
# DEV-LOG
22

3+
## Sentry 错误上报集成 (2026-04-03)
4+
5+
恢复反编译过程中被移除的 Sentry 集成。通过 `SENTRY_DSN` 环境变量控制,未设置时所有函数为 no-op,不影响正常运行。
6+
7+
**新增文件:**
8+
9+
| 文件 | 说明 |
10+
|------|------|
11+
| `src/utils/sentry.ts` | 核心模块:`initSentry()``captureException()``setTag()``setUser()``closeSentry()``beforeSend` 过滤 auth headers 等敏感信息;忽略 ECONNREFUSED/AbortError 等非 actionable 错误 |
12+
13+
**修改文件:**
14+
15+
| 文件 | 变更 |
16+
|------|------|
17+
| `src/utils/errorLogSink.ts` | `logErrorImpl` 末尾调用 `captureException()`,所有经 `logError()` 的错误自动上报 |
18+
| `src/components/SentryErrorBoundary.ts` | 添加 `componentDidCatch`,React 组件渲染错误上报到 Sentry(含 componentStack) |
19+
| `src/entrypoints/init.ts` | 网络配置后调用 `initSentry()` |
20+
| `src/utils/gracefulShutdown.ts` | 优雅关闭时 flush Sentry 事件 |
21+
| `src/screens/REPL.tsx:2809` | `fireCompanionObserver` 调用增加 `typeof` 防护,BUDDY feature 启用时不报错(TODO: 待实现) |
22+
| `package.json` | devDependencies 新增 `@sentry/node` |
23+
24+
**用法:** `SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx bun run dev`
25+
26+
---
27+
28+
## 默认关闭自动更新 (2026-04-03)
29+
30+
修改 `src/utils/config.ts``getAutoUpdaterDisabledReason()`,在原有检查逻辑前插入默认关闭逻辑。未设置 `ENABLE_AUTOUPDATER=1` 时,自动更新始终返回 `{ type: 'config' }` 被禁用。
31+
32+
**启用方式:** `ENABLE_AUTOUPDATER=1 bun run dev`
33+
34+
**原因:** 本项目为逆向工程/反编译版本,自动更新会覆盖本地修改的代码。
35+
36+
**同时新增文档:** `docs/auto-updater.md` — 自动更新机制完整审计,涵盖三种安装类型的更新策略、后台轮询、版本门控、原生安装器架构、文件锁、配置项等。
37+
38+
---
39+
340
## WebSearch Bing 适配器补全 (2026-04-03)
441

542
原始 `WebSearchTool` 仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在非官方 API 端点(第三方代理)下搜索功能不可用。本次改动引入适配器架构,新增 Bing 搜索页面解析作为 fallback。

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
- [x] 移除牢 A 的反蒸馏代码!!!
1919
- [x] 补全 web search 能力(用的 Bing 搜索)!!!
2020
- [x] 支持 Debug
21+
- [x] 关闭自动更新;
22+
- [x] 添加 sentry 错误上报支持
2123
- [ ] V5 大规模重构石山代码, 全面模块分包
2224
- [ ] V5 将会为全新分支, 届时 main 分支将会封存为历史版本
2325

bun.lock

Lines changed: 117 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# 遥测与远程配置下发系统审计(除 Sentry 外)
2+
3+
## 1. Datadog 日志
4+
5+
**文件**: `src/services/analytics/datadog.ts`
6+
7+
- **端点**: `https://http-intake.logs.us5.datadoghq.com/api/v2/logs`
8+
- **客户端 token**: `pubbbf48e6d78dae54bceaa4acf463299bf`
9+
- **行为**: 批量发送日志(15s flush 间隔,100 条上限),仅限 1P(直连 Anthropic API)用户
10+
- **事件白名单**: `tengu_*` 系列事件(启动、错误、OAuth、工具调用等 ~35 种)
11+
- **基线数据**: 收集 model、platform、arch、version、userBucket(用户 hash 到 30 个桶)等
12+
- **仅限**: `NODE_ENV === 'production'`
13+
14+
## 2. 1P 事件日志(BigQuery)
15+
16+
**文件**: `src/services/analytics/firstPartyEventLogger.ts` + `firstPartyEventLoggingExporter.ts`
17+
18+
- **端点**: `https://api.anthropic.com/api/event_logging/batch`(staging 可切换)
19+
- **行为**: 使用 OpenTelemetry SDK 的 `BatchLogRecordProcessor`,批量导出到 Anthropic 自有的 BQ 管道
20+
- **数据**: 完整事件 metadata(session、model、env context、用户数据、subscription type 等)
21+
- **弹性**: 本地磁盘持久化失败事件(JSONL),二次退避重试,最多 8 次尝试
22+
- **Proto schema**: 事件序列化为 `ClaudeCodeInternalEvent` / `GrowthbookExperimentEvent` protobuf 格式
23+
- **Auth fallback**: 401 时自动去掉 auth header 重试
24+
25+
## 3. GrowthBook 远程 Feature Flags / 动态配置
26+
27+
**文件**: `src/services/analytics/growthbook.ts`
28+
29+
- **服务端**: `https://api.anthropic.com/`(remote eval 模式)
30+
- **行为**: 启动时拉取全量 feature flags,每 6h(外部用户)/ 20min(ant)定时刷新
31+
- **磁盘缓存**: feature values 写入 `~/.claude.json``cachedGrowthBookFeatures`
32+
- **用途**:
33+
- 控制 Datadog 开关(`tengu_log_datadog_events`
34+
- 控制事件采样率(`tengu_event_sampling_config`
35+
- 控制 sink killswitch(`tengu_frond_boric`
36+
- 控制 BQ batch 配置(`tengu_1p_event_batch_config`
37+
- 控制版本上限/自动更新 kill switch
38+
- 控制远程管理设置的安全检查 gate
39+
- **用户属性**: 发送 deviceId, sessionId, organizationUUID, accountUUID, email, subscriptionType 等
40+
41+
## 4. Remote Managed Settings(企业远程配置下发)
42+
43+
**文件**: `src/services/remoteManagedSettings/index.ts`
44+
45+
- **端点**: `{BASE_API_URL}/api/claude_code/settings`
46+
- **行为**: 企业用户配置下发,支持 ETag/304 缓存,每小时后台轮询
47+
- **安全**: 变更包含"危险设置"时弹窗让用户确认
48+
- **适用**: API key 用户全部可拉取;OAuth 用户仅 Enterprise/C4E/Team
49+
- **Fail-open**: 请求失败时使用本地缓存,无缓存则跳过
50+
51+
## 5. Settings Sync(设置同步)
52+
53+
**文件**: `src/services/settingsSync/index.ts`
54+
55+
- **端点**: `{BASE_API_URL}/api/claude_code/user_settings`
56+
- **行为**: CLI 上传本地设置/memory 到远程;CCR 模式从远程下载
57+
- **同步内容**: userSettings、userMemory、projectSettings、projectMemory
58+
- **Feature gate**: `UPLOAD_USER_SETTINGS` / `DOWNLOAD_USER_SETTINGS`
59+
- **文件大小限制**: 500KB/文件
60+
61+
## 6. OpenTelemetry 三方遥测
62+
63+
**文件**: `src/utils/telemetry/instrumentation.ts`
64+
65+
- **行为**: 完整的 OTEL SDK 初始化,支持 metrics / logs / traces 三种信号
66+
- **协议**: gRPC / http-json / http-protobuf(通过 `OTEL_EXPORTER_OTLP_PROTOCOL` 选择)
67+
- **exporter**: console / otlp / prometheus
68+
- **触发**: `CLAUDE_CODE_ENABLE_TELEMETRY=1` 环境变量
69+
- **增强 trace**: `feature('ENHANCED_TELEMETRY_BETA')` + GrowthBook gate `enhanced_telemetry_beta`
70+
71+
## 7. BigQuery Metrics Exporter(内部指标)
72+
73+
**文件**: `src/utils/telemetry/bigqueryExporter.ts`
74+
75+
- **端点**: `https://api.anthropic.com/api/claude_code/metrics`
76+
- **行为**: 定期(5min 间隔)导出 OTel metrics 到内部 BQ
77+
- **适用**: API 客户、C4E/Team 订阅者
78+
- **组织级 opt-out**: 通过 `checkMetricsEnabled()` API 查询(见下方第 8 项)
79+
80+
## 8. 组织级 Metrics Opt-out 查询
81+
82+
**文件**: `src/services/api/metricsOptOut.ts`
83+
84+
- **端点**: `https://api.anthropic.com/api/claude_code/organizations/metrics_enabled`
85+
- **行为**: 查询组织是否启用了 metrics,二级缓存(内存 1h + 磁盘 24h)
86+
- **作用**: 控制 BigQuery metrics exporter 是否导出
87+
88+
## 9. Startup Profiling
89+
90+
**文件**: `src/utils/startupProfiler.ts`
91+
92+
- **行为**: 采样启动性能数据(100% ant / 0.5% 外部),通过 `logEvent('tengu_startup_perf')` 上报
93+
- **详细模式**: `CLAUDE_CODE_PROFILE_STARTUP=1` 输出完整性能报告到文件
94+
95+
## 10. Beta Session Tracing
96+
97+
**文件**: `src/utils/telemetry/betaSessionTracing.ts`
98+
99+
- **行为**: 详细调试 trace,发送 system prompt、model output、tool schema 等
100+
- **触发**: `ENABLE_BETA_TRACING_DETAILED=1` + `BETA_TRACING_ENDPOINT`
101+
- **外部用户**: SDK/headless 模式自动启用,交互模式需要 GrowthBook gate `tengu_trace_lantern`
102+
103+
## 11. Bridge Poll Config(远程轮询间隔配置)
104+
105+
**文件**: `src/bridge/pollConfig.ts`
106+
107+
- **行为**: 从 GrowthBook 拉取 bridge 轮询间隔配置(`tengu_bridge_poll_interval_config`
108+
- **控制**: 单会话和多会话的各种 poll interval
109+
110+
## 12. Plugin/MCP 遥测
111+
112+
**文件**: `src/utils/plugins/fetchTelemetry.ts`
113+
114+
- **行为**: 记录 plugin/marketplace 的网络请求(安装计数、marketplace clone/pull 等)
115+
- **事件**: `tengu_plugin_remote_fetch`,包含 host(已脱敏)、outcome、duration
116+
117+
---
118+
119+
## 全局禁用方式
120+
121+
```bash
122+
# 禁用所有遥测(Datadog + 1P + 调查问卷)
123+
DISABLE_TELEMETRY=1
124+
125+
# 更激进:禁用所有非必要网络(包括自动更新、grove、release notes 等)
126+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
127+
128+
# 3P 提供商自动禁用
129+
CLAUDE_CODE_USE_BEDROCK=1 # 或 VERTEX/FOUNDRY
130+
```
131+
132+
`src/utils/privacyLevel.ts` 是集中控制点,三个级别:`default < no-telemetry < essential-traffic`
133+
134+
---
135+
136+
## 数据流架构
137+
138+
```
139+
用户操作 → logEvent()
140+
141+
sink.ts (路由层)
142+
↙ ↘
143+
trackDatadogEvent() logEventTo1P()
144+
↓ ↓
145+
Datadog HTTP API OTel BatchLogRecordProcessor
146+
(us5.datadoghq.com) ↓
147+
FirstPartyEventLoggingExporter
148+
149+
api.anthropic.com/api/event_logging/batch
150+
151+
BigQuery (ClaudeCodeInternalEvent proto)
152+
```
153+
154+
GrowthBook 作为独立通道,同时驱动上述两个 sink 的开关和配置。

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"@opentelemetry/sdk-metrics": "^2.6.1",
9191
"@opentelemetry/sdk-trace-base": "^2.6.1",
9292
"@opentelemetry/semantic-conventions": "^1.40.0",
93+
"@sentry/node": "^10.47.0",
9394
"@smithy/core": "^3.23.13",
9495
"@smithy/node-http-handler": "^4.5.1",
9596
"@types/bun": "^1.3.11",

src/components/SentryErrorBoundary.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import * as React from 'react'
2+
import { captureException } from 'src/utils/sentry.js'
23

34
interface Props {
45
children: React.ReactNode
6+
/** Optional label for identifying which component boundary caught the error */
7+
name?: string
58
}
69

710
interface State {
@@ -18,6 +21,13 @@ export class SentryErrorBoundary extends React.Component<Props, State> {
1821
return { hasError: true }
1922
}
2023

24+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
25+
captureException(error, {
26+
componentBoundary: this.props.name || 'SentryErrorBoundary',
27+
componentStack: errorInfo.componentStack,
28+
})
29+
}
30+
2131
render(): React.ReactNode {
2232
if (this.state.hasError) {
2333
return null

src/entrypoints/init.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { configureGlobalAgents } from '../utils/proxy.js'
4848
import { isBetaTracingEnabled } from '../utils/telemetry/betaSessionTracing.js'
4949
import { getTelemetryAttributes } from '../utils/telemetryAttributes.js'
5050
import { setShellIfWindows } from '../utils/windowsPaths.js'
51+
import { initSentry } from '../utils/sentry.js'
5152

5253
// initialize1PEventLogging is dynamically imported to defer OpenTelemetry sdk-logs/resources
5354

@@ -150,6 +151,9 @@ export const init = memoize(async (): Promise<void> => {
150151
logForDebugging('[init] configureGlobalAgents complete')
151152
profileCheckpoint('init_network_configured')
152153

154+
// Initialize Sentry for error reporting (no-op if SENTRY_DSN not set)
155+
initSentry()
156+
153157
// Preconnect to the Anthropic API — overlap TCP+TLS handshake
154158
// (~100-200ms) with the ~100ms of action-handler work before the API
155159
// request. After CA certs + proxy agents are configured so the warmed

src/screens/REPL.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2805,7 +2805,8 @@ export function REPL({
28052805
})) {
28062806
onQueryEvent(event);
28072807
}
2808-
if (feature('BUDDY')) {
2808+
// TODO: implement fireCompanionObserver — companion model reaction after each query turn
2809+
if (feature('BUDDY') && typeof fireCompanionObserver === 'function') {
28092810
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
28102811
...prev,
28112812
companionReaction: reaction as string | undefined

src/utils/errorLogSink.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { logForDebugging } from './debug.js'
2020
import { getFsImplementation } from './fsOperations.js'
2121
import { attachErrorLogSink, dateToFilename } from './log.js'
2222
import { jsonStringify } from './slowOperations.js'
23+
import { captureException } from './sentry.js'
2324

2425
const DATE = dateToFilename(new Date())
2526

@@ -171,6 +172,9 @@ function logErrorImpl(error: Error): void {
171172
appendToLog(getErrorsPath(), {
172173
error: `${context}${errorStr}`,
173174
})
175+
176+
// Also report to Sentry (no-op if not initialized)
177+
captureException(error)
174178
}
175179

176180
/**

src/utils/gracefulShutdown.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { logForDiagnosticsNoPII } from './diagLogs.js'
4242
import { isEnvTruthy } from './envUtils.js'
4343
import { getCurrentSessionTitle, sessionIdExists } from './sessionStorage.js'
4444
import { sleep } from './sleep.js'
45+
import { closeSentry } from './sentry.js'
4546
import { profileReport } from './startupProfiler.js'
4647

4748
/**
@@ -503,7 +504,7 @@ export async function gracefulShutdown(
503504
// Lost analytics on slow networks are acceptable; a hanging exit is not.
504505
try {
505506
await Promise.race([
506-
Promise.all([shutdown1PEventLogging(), shutdownDatadog()]),
507+
Promise.all([shutdown1PEventLogging(), shutdownDatadog(), closeSentry(2000)]),
507508
sleep(500),
508509
])
509510
} catch {

0 commit comments

Comments
 (0)