0%

Claude Code 源码解析 (19):日志与遥测系统的设计

Claude Code 源码解析 (19):日志与遥测系统的设计

导读: 这是 Claude Code 20 个功能特性源码解析系列的第 19 篇,深入分析日志遥测系统的架构设计。


📋 目录

  1. 问题引入:为什么需要日志遥测?
  2. 技术原理:日志遥测架构
  3. 设计思想:为什么这样设计
  4. 解决方案:完整实现详解
  5. OpenClaw 最佳实践
  6. 总结

问题引入:为什么需要日志遥测?

痛点场景

场景 1:问题难以排查

1
2
3
4
5
6
7
8
9
用户:"AI 刚才卡住了"

开发者:
- 什么时候卡的?
- 做了什么操作?
- 有什么错误信息?

→ 没有日志,无法定位
→ 只能猜

场景 2:性能问题难发现

1
2
3
4
5
6
7
8
9
用户:"感觉有点慢"

开发者:
- 哪里慢?
- 慢多少?
- 一直慢还是偶尔慢?

→ 没有性能数据
→ 无法优化

场景 3:使用情况不了解

1
2
3
4
5
6
7
产品经理:
- 哪些功能最常用?
- 用户通常在什么场景使用?
- 哪些功能没人用?

→ 没有使用数据
→ 无法决策

核心问题

设计 AI 助手的日志遥测系统时,面临以下挑战:

  1. 隐私问题

    • 如何记录有用的日志?
    • 如何保护用户隐私?
  2. 性能问题

    • 日志记录本身影响性能
    • 如何平衡详细度与性能?
  3. 可用性问题

    • 海量日志如何分析?
    • 如何快速定位问题?
  4. 合规问题

    • 如何符合数据保护法规?
    • 如何获得用户同意?

Claude Code 用结构化日志 + 隐私优先遥测解决了这些问题。


技术原理:日志遥测架构

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 │
│ (AI Assistant) │
└─────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Logger (日志记录器) │
│ - 结构化日志 │
│ - 日志级别 │
│ - 上下文注入 │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Log Processors (日志处理器) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Console Processor │ │
│ │ - 开发调试 │ │
│ │ - 彩色输出 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ File Processor │ │
│ │ - 持久化存储 │ │
│ │ - 日志轮转 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Remote Processor │ │
│ │ - 遥测上报 │ │
│ │ - 隐私过滤 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ Telemetry System (遥测系统) │
│ - 性能追踪 │
│ - 错误监控 │
│ - 使用统计 │
│ - 隐私保护 │
└─────────────────────────────────────────────────────────────┘

结构化日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
interface LogEntry {
// 基本信息
timestamp: Date;
level: LogLevel;
message: string;

// 上下文
context: LogContext;

// 结构化数据
data?: Record<string, any>;

// 错误信息
error?: {
name: string;
message: string;
stack?: string;
};

// 追踪信息
trace?: {
traceId: string;
spanId: string;
parentId?: string;
};
}

type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';

interface LogContext {
// 用户信息 (脱敏)
userId?: string;
sessionId?: string;

// 环境信息
environment: 'development' | 'production';
version: string;
platform: string;

// 请求信息
requestId?: string;
tool?: string;
action?: string;
}

class StructuredLogger {
private level: LogLevel;
private context: LogContext;
private processors: LogProcessor[];

constructor(config: LoggerConfig) {
this.level = config.level || 'info';
this.context = config.context;
this.processors = config.processors || [];
}

// 记录日志
log(level: LogLevel, message: string, data?: any): void {
// 检查日志级别
if (!this.shouldLog(level)) {
return;
}

// 创建日志条目
const entry: LogEntry = {
timestamp: new Date(),
level,
message,
context: {
...this.context,
requestId: this.generateRequestId(),
},
data: this.sanitizeData(data),
trace: this.getCurrentTrace(),
};

// 添加错误信息
if (data instanceof Error) {
entry.error = {
name: data.name,
message: data.message,
stack: data.stack,
};
}

// 发送到处理器
for (const processor of this.processors) {
processor.process(entry);
}
}

// 便捷方法
debug(message: string, data?: any): void {
this.log('debug', message, data);
}

info(message: string, data?: any): void {
this.log('info', message, data);
}

warn(message: string, data?: any): void {
this.log('warn', message, data);
}

error(message: string, data?: any): void {
this.log('error', message, data);
}

fatal(message: string, data?: any): void {
this.log('fatal', message, data);
}

// 创建子日志器 (添加上下文)
child(context: Partial<LogContext>): StructuredLogger {
return new StructuredLogger({
...this.config,
context: { ...this.context, ...context },
});
}

// 数据脱敏
private sanitizeData(data: any): any {
if (!data) return data;

const sensitiveKeys = [
'password',
'secret',
'token',
'apiKey',
'api_key',
'credential',
];

const sanitized = { ...data };

for (const key of sensitiveKeys) {
if (key in sanitized) {
sanitized[key] = '[REDACTED]';
}
}

return sanitized;
}

private shouldLog(level: LogLevel): boolean {
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error', 'fatal'];
return levels.indexOf(level) >= levels.indexOf(this.level);
}

private generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

private getCurrentTrace(): LogEntry['trace'] {
// 从异步上下文获取追踪信息
return {
traceId: globalThis.currentTraceId || this.generateRequestId(),
spanId: `span_${Math.random().toString(36).substr(2, 9)}`,
};
}
}

interface LogProcessor {
process(entry: LogEntry): void;
}

文件日志处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
class FileLogProcessor implements LogProcessor {
private filePath: string;
private maxSize: number;
private maxFiles: number;
private stream: fs.WriteStream | null = null;

constructor(config: FileLogConfig) {
this.filePath = config.filePath;
this.maxSize = config.maxSize || 10 * 1024 * 1024; // 10MB
this.maxFiles = config.maxFiles || 5;
}

process(entry: LogEntry): void {
// 确保目录存在
this.ensureDirectory();

// 检查是否需要轮转
this.checkRotation();

// 写入日志
const line = this.formatEntry(entry) + '\n';

if (!this.stream) {
this.stream = fs.createWriteStream(this.filePath, { flags: 'a' });
}

this.stream.write(line);
}

private formatEntry(entry: LogEntry): string {
// JSON 格式
return JSON.stringify({
timestamp: entry.timestamp.toISOString(),
level: entry.level,
message: entry.message,
context: entry.context,
data: entry.data,
error: entry.error,
trace: entry.trace,
});
}

private ensureDirectory(): void {
const dir = path.dirname(this.filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}

private checkRotation(): void {
if (!fs.existsSync(this.filePath)) {
return;
}

const stats = fs.statSync(this.filePath);

if (stats.size >= this.maxSize) {
this.rotate();
}
}

private rotate(): void {
// 关闭当前流
if (this.stream) {
this.stream.end();
this.stream = null;
}

// 轮转文件
for (let i = this.maxFiles - 1; i >= 1; i--) {
const oldPath = `${this.filePath}.${i}`;
const newPath = `${this.filePath}.${i + 1}`;

if (fs.existsSync(oldPath)) {
fs.renameSync(oldPath, newPath);
}
}

// 移动当前文件
fs.renameSync(this.filePath, `${this.filePath}.1`);

// 删除最旧文件
const oldestPath = `${this.filePath}.${this.maxFiles + 1}`;
if (fs.existsSync(oldestPath)) {
fs.unlinkSync(oldestPath);
}
}

dispose(): void {
if (this.stream) {
this.stream.end();
this.stream = null;
}
}
}

interface FileLogConfig {
filePath: string;
maxSize?: number;
maxFiles?: number;
}

性能追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class PerformanceTracker {
private metrics: Map<string, Metric[]> = new Map();
private activeSpans: Map<string, Span> = new Map();

// 开始追踪
startSpan(name: string, options?: SpanOptions): Span {
const span: Span = {
id: generateSpanId(),
name,
startTime: Date.now(),
attributes: options?.attributes || {},
parentId: options?.parentId,
};

this.activeSpans.set(span.id, span);

return span;
}

// 结束追踪
endSpan(spanId: string, attributes?: Record<string, any>): void {
const span = this.activeSpans.get(spanId);

if (!span) {
return;
}

span.endTime = Date.now();
span.duration = span.endTime - span.startTime;
span.attributes = { ...span.attributes, ...attributes };

// 记录指标
this.recordMetric(span.name, {
duration: span.duration,
timestamp: span.startTime,
attributes: span.attributes,
});

this.activeSpans.delete(spanId);
}

// 记录指标
recordMetric(name: string, metric: Metric): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}

this.metrics.get(name)!.push(metric);

// 限制内存中的指标数量
if (this.metrics.get(name)!.length > 1000) {
this.metrics.get(name)!.shift();
}
}

// 获取性能报告
getReport(): PerformanceReport {
const report: PerformanceReport = {
spans: [],
summaries: [],
percentiles: {},
};

// 计算每个操作的统计
for (const [name, metrics] of this.metrics.entries()) {
const durations = metrics.map(m => m.duration);

report.summaries.push({
name,
count: metrics.length,
min: Math.min(...durations),
max: Math.max(...durations),
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
p50: this.percentile(durations, 50),
p95: this.percentile(durations, 95),
p99: this.percentile(durations, 99),
});
}

return report;
}

private percentile(sorted: number[], p: number): number {
const index = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[index] || 0;
}
}

interface Span {
id: string;
name: string;
startTime: number;
endTime?: number;
duration?: number;
attributes: Record<string, any>;
parentId?: string;
}

interface SpanOptions {
attributes?: Record<string, any>;
parentId?: string;
}

interface Metric {
duration: number;
timestamp: number;
attributes: Record<string, any>;
}

interface PerformanceReport {
spans: Span[];
summaries: MetricSummary[];
percentiles: Record<string, number>;
}

interface MetricSummary {
name: string;
count: number;
min: number;
max: number;
avg: number;
p50: number;
p95: number;
p99: number;
}

错误监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class ErrorMonitor {
private errors: ErrorReport[] = [];
private maxReports = 100;
private reporter: ErrorReporter;

constructor(config: ErrorMonitorConfig) {
this.reporter = config.reporter;
this.setupGlobalHandler();
}

// 记录错误
report(error: Error, context?: ErrorContext): void {
const report: ErrorReport = {
id: generateErrorId(),
timestamp: new Date(),
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
context: {
...context,
userId: this.sanitizeUserId(context?.userId),
},
metrics: this.collectMetrics(),
};

this.errors.push(report);

// 限制存储数量
if (this.errors.length > this.maxReports) {
this.errors.shift();
}

// 上报错误
this.reporter.send(report);
}

// 设置全局错误处理
private setupGlobalHandler(): void {
// Node.js 未捕获异常
process.on('uncaughtException', (error) => {
this.report(error, { type: 'uncaught_exception' });
});

// Node.js 未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
this.report(reason as Error, { type: 'unhandled_rejection' });
});

// 浏览器错误
if (typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
this.report(event.error, { type: 'browser_error' });
});
}
}

// 收集错误时的指标
private collectMetrics(): ErrorMetrics {
return {
memoryUsage: process.memoryUsage(),
cpuUsage: process.cpuUsage(),
uptime: process.uptime(),
};
}

// 获取错误报告
getReports(filter?: ErrorFilter): ErrorReport[] {
let reports = [...this.errors];

if (filter?.type) {
reports = reports.filter(r => r.context?.type === filter.type);
}

if (filter?.since) {
reports = reports.filter(r => r.timestamp >= filter.since);
}

return reports.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}

// 获取错误统计
getSummary(): ErrorSummary {
const reports = this.errors;

return {
total: reports.length,
byType: this.groupBy(reports, r => r.context?.type),
byError: this.groupBy(reports, r => r.error.name),
recentTrend: this.calculateTrend(reports),
};
}

private groupBy<T>(items: T[], keyFn: (item: T) => string): Record<string, number> {
return items.reduce((acc, item) => {
const key = keyFn(item);
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {} as Record<string, number>);
}

private calculateTrend(reports: ErrorReport[]): 'increasing' | 'stable' | 'decreasing' {
const now = Date.now();
const oneHourAgo = now - 3600000;
const twoHoursAgo = now - 7200000;

const recentCount = reports.filter(r => r.timestamp.getTime() > oneHourAgo).length;
const previousCount = reports.filter(
r => r.timestamp.getTime() > twoHoursAgo && r.timestamp.getTime() <= oneHourAgo
).length;

if (recentCount > previousCount * 1.5) {
return 'increasing';
}
if (recentCount < previousCount * 0.5) {
return 'decreasing';
}
return 'stable';
}

private sanitizeUserId(userId?: string): string | undefined {
if (!userId) return undefined;
// 只保留部分 ID,保护隐私
return userId.substring(0, 8) + '***';
}
}

interface ErrorReport {
id: string;
timestamp: Date;
error: {
name: string;
message: string;
stack?: string;
};
context?: ErrorContext;
metrics: ErrorMetrics;
}

interface ErrorContext {
type?: string;
userId?: string;
sessionId?: string;
action?: string;
tool?: string;
}

interface ErrorMetrics {
memoryUsage: NodeJS.MemoryUsage;
cpuUsage: NodeJS.CpuUsage;
uptime: number;
}

interface ErrorSummary {
total: number;
byType: Record<string, number>;
byError: Record<string, number>;
recentTrend: 'increasing' | 'stable' | 'decreasing';
}

interface ErrorFilter {
type?: string;
since?: Date;
}

interface ErrorReporter {
send(report: ErrorReport): void;
}

隐私保护遥测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class PrivacyAwareTelemetry {
private config: TelemetryConfig;
private consentManager: ConsentManager;

constructor(config: TelemetryConfig) {
this.config = config;
this.consentManager = new ConsentManager();
}

// 记录使用事件
trackEvent(event: TelemetryEvent): void {
// 检查用户同意
if (!this.consentManager.hasConsent(event.category)) {
return;
}

// 脱敏处理
const sanitized = this.sanitizeEvent(event);

// 批量上报
this.batchAndSend(sanitized);
}

// 脱敏事件
private sanitizeEvent(event: TelemetryEvent): TelemetryEvent {
const sanitized = { ...event };

// 移除个人身份信息
delete sanitized.userId;
delete sanitized.email;
delete sanitized.ipAddress;

// 脱敏内容
if (sanitized.data?.content) {
sanitized.data.content = this.hashContent(sanitized.data.content);
}

// 添加随机噪声 (差分隐私)
if (this.config.differentialPrivacy) {
sanitized.timestamp = this.addNoise(sanitized.timestamp);
}

return sanitized;
}

private hashContent(content: string): string {
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
}

private addNoise(value: number): number {
// 添加拉普拉斯噪声
const noise = this.laplaceNoise(1.0);
return Math.round(value + noise);
}

private laplaceNoise(scale: number): number {
const u = Math.random() - 0.5;
return -Math.sign(u) * scale * Math.log(1 - 2 * Math.abs(u));
}

// 批量上报
private batchAndSend(event: TelemetryEvent): void {
// 添加到队列
this.eventQueue.push(event);

// 达到批次大小或时间间隔时发送
if (this.eventQueue.length >= this.config.batchSize) {
this.flushQueue();
} else if (!this.flushScheduled) {
this.flushScheduled = true;
setTimeout(() => this.flushQueue(), this.config.flushInterval);
}
}

private eventQueue: TelemetryEvent[] = [];
private flushScheduled = false;

private flushQueue(): void {
if (this.eventQueue.length === 0) {
this.flushScheduled = false;
return;
}

const batch = [...this.eventQueue];
this.eventQueue = [];
this.flushScheduled = false;

// 发送到遥测服务器
this.sendToServer(batch);
}

private async sendToServer(events: TelemetryEvent[]): Promise<void> {
try {
await fetch(this.config.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events }),
});
} catch (error) {
console.error('Telemetry send failed:', error);
// 失败时保留事件到本地
this.eventQueue.unshift(...events);
}
}
}

interface TelemetryEvent {
name: string;
category: 'usage' | 'performance' | 'error';
timestamp: number;
userId?: string;
sessionId?: string;
data?: Record<string, any>;
}

interface TelemetryConfig {
endpoint: string;
batchSize: number;
flushInterval: number;
differentialPrivacy: boolean;
}

class ConsentManager {
private consents: Map<string, boolean> = new Map();

hasConsent(category: string): boolean {
return this.consents.get(category) || false;
}

setConsent(category: string, granted: boolean): void {
this.consents.set(category, granted);
this.persistConsent();
}

private persistConsent(): void {
// 保存到本地存储
localStorage.setItem('telemetry_consents', JSON.stringify(
Object.fromEntries(this.consents)
));
}
}

设计思想:为什么这样设计

思想 1:结构化日志

问题: 纯文本日志难以分析。

解决: 结构化日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 结构化日志
{
"timestamp": "2026-04-03T21:40:00Z",
"level": "error",
"message": "Tool execution failed",
"context": {
"tool": "bash",
"sessionId": "sess_xxx"
},
"error": {
"name": "TimeoutError",
"message": "Command timed out"
}
}

设计智慧:

结构化日志让机器可以分析日志。

思想 2:日志分级

问题: 所有日志同等重要会淹没关键信息。

解决: 日志分级。

1
2
3
4
5
DEBUG → 开发调试
INFO → 一般信息
WARN → 警告信息
ERROR → 错误信息
FATAL → 致命错误

思想 3:隐私优先

问题: 遥测可能泄露隐私。

解决: 隐私优先设计。

1
2
3
4
// 默认不收集敏感信息
// 用户明确同意才收集
// 收集后自动脱敏
// 支持差分隐私

思想 4:性能无感

问题: 日志记录影响性能。

解决: 异步批量处理。

1
2
3
// 异步写入,不阻塞主线程
// 批量上报,减少网络请求
// 内存限制,避免泄漏

思想 5:可观测性

问题: 系统内部状态不可见。

解决: 完整可观测性。

1
2
3
日志 (Logs)     → 发生了什么
指标 (Metrics) → 性能如何
追踪 (Traces) → 请求路径

解决方案:完整实现详解

TelemetryManager 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class TelemetryManager {
private logger: StructuredLogger;
private tracker: PerformanceTracker;
private errorMonitor: ErrorMonitor;
private telemetry: PrivacyAwareTelemetry;

constructor(config: TelemetryManagerConfig) {
this.logger = new StructuredLogger({
level: config.logLevel,
context: {
environment: config.environment,
version: config.version,
},
processors: [
new ConsoleLogProcessor(),
new FileLogProcessor({ filePath: config.logFile }),
],
});

this.tracker = new PerformanceTracker();

this.errorMonitor = new ErrorMonitor({
reporter: new RemoteErrorReporter(config.errorEndpoint),
});

this.telemetry = new PrivacyAwareTelemetry({
endpoint: config.telemetryEndpoint,
batchSize: 20,
flushInterval: 30000,
differentialPrivacy: true,
});
}

// 记录工具调用
trackToolCall(tool: string, input: any, result: any, duration: number): void {
this.logger.info('Tool executed', {
tool,
duration,
success: !result.error,
});

this.tracker.recordMetric(`tool.${tool}`, {
duration,
timestamp: Date.now(),
attributes: { success: !result.error },
});

this.telemetry.trackEvent({
name: 'tool_executed',
category: 'usage',
timestamp: Date.now(),
data: { tool, duration, success: !result.error },
});
}

// 记录 API 调用
trackApiCall(endpoint: string, duration: number, tokens: number): void {
this.logger.debug('API call', {
endpoint,
duration,
tokens,
});

this.telemetry.trackEvent({
name: 'api_call',
category: 'usage',
timestamp: Date.now(),
data: { endpoint, duration, tokens },
});
}

// 记录错误
trackError(error: Error, context: ErrorContext): void {
this.logger.error('Error occurred', {
error: error.message,
stack: error.stack,
context,
});

this.errorMonitor.report(error, context);
}

// 开始性能追踪
startSpan(name: string, options?: SpanOptions): Span {
return this.tracker.startSpan(name, options);
}

// 结束性能追踪
endSpan(spanId: string, attributes?: Record<string, any>): void {
this.tracker.endSpan(spanId, attributes);
}

// 获取性能报告
getPerformanceReport(): PerformanceReport {
return this.tracker.getReport();
}

// 获取错误报告
getErrorReports(filter?: ErrorFilter): ErrorReport[] {
return this.errorMonitor.getReports(filter);
}

// 设置遥测同意
setTelemetryConsent(category: string, granted: boolean): void {
this.telemetry.consentManager.setConsent(category, granted);
this.logger.info('Telemetry consent updated', { category, granted });
}

// 清理资源
dispose(): void {
this.logger.dispose();
this.telemetry.flushQueue();
}
}

配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# ~/.openclaw/config/telemetry.yaml

# 日志配置
logging:
# 日志级别
level: info

# 日志文件
file: ~/.openclaw/logs/openclaw.log

# 日志轮转
rotation:
max_size: 10MB
max_files: 5

# 日志格式
format: json

# 性能追踪
performance:
# 启用追踪
enabled: true

# 慢操作阈值 (ms)
slow_threshold: 1000

# 报告间隔
report_interval: 3600 # 每小时

# 错误监控
errors:
# 启用错误监控
enabled: true

# 错误上报端点
endpoint: https://telemetry.openclaw.ai/errors

# 采样率
sample_rate: 1.0 # 100% 上报

# 遥测配置
telemetry:
# 启用遥测
enabled: true

# 上报端点
endpoint: https://telemetry.openclaw.ai/events

# 隐私保护
privacy:
# 差分隐私
differential_privacy: true

# 脱敏
sanitize_data: true

# 用户同意
require_consent: true

# 批量设置
batch:
size: 20
interval: 30000 # 30 秒

OpenClaw 最佳实践

实践 1:查看日志

1
2
3
4
5
6
7
8
9
10
11
# 查看实时日志
openclaw logs tail

# 查看错误日志
openclaw logs --level error

# 搜索日志
openclaw logs --grep "timeout"

# 查看特定工具日志
openclaw logs --tool bash

实践 2:查看性能报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看性能报告
openclaw telemetry performance

# 输出:
性能报告 (最近 1 小时):
─────────────────────────────────────
工具调用:
bash: 50 次,平均 234ms, p95 890ms
file_read: 120 次,平均 45ms, p95 120ms
file_edit: 35 次,平均 567ms, p95 1200ms

API 调用:
chat: 85 次,平均 1234ms, p95 3400ms

慢操作 Top 5:
1. file_edit: 2345ms
2. bash: 1890ms
3. chat: 1567ms

实践 3:查看错误报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看错误报告
openclaw telemetry errors

# 输出:
错误报告 (最近 24 小时):
─────────────────────────────────────
总错误数:15
趋势:稳定

按类型:
tool_error: 10
api_error: 4
system_error: 1

按错误:
TimeoutError: 8
NetworkError: 4
PermissionError: 3

实践 4:配置遥测同意

1
2
3
4
5
6
7
8
9
10
11
12
# 查看遥测设置
openclaw telemetry consent

# 输出:
遥测同意状态:
─────────────────────────────────────
使用统计:已同意
性能数据:已同意
错误报告:未同意

# 设置同意
openclaw telemetry consent set error true

实践 5:导出日志分析

1
2
3
4
5
# 导出日志用于分析
openclaw logs export --format json --output logs.json

# 生成分析报告
openclaw telemetry report --period 7d --output report.pdf

总结

核心要点

  1. 结构化日志 - 机器可分析的日志格式
  2. 日志分级 - 不同级别不同用途
  3. 隐私优先 - 默认不收集敏感信息
  4. 性能无感 - 异步批量处理
  5. 完整可观测性 - 日志 + 指标 + 追踪

设计智慧

好的日志遥测让系统”透明且可诊断”。

Claude Code 的日志遥测设计告诉我们:

  • 结构化日志便于自动化分析
  • 隐私保护是遥测的前提
  • 性能追踪帮助持续优化
  • 错误监控提前发现问题

日志级别使用指南

级别 用途 示例
DEBUG 开发调试 详细执行过程
INFO 一般信息 操作完成、状态变更
WARN 警告信息 可恢复的错误
ERROR 错误信息 操作失败
FATAL 致命错误 系统崩溃

下一步

  • 配置日志级别
  • 启用性能追踪
  • 设置错误告警
  • 配置遥测同意

系列文章:

  • [1] Bash 命令执行的安全艺术 (已发布)
  • [2] 差异编辑的设计艺术 (已发布)
  • [3] 文件搜索的底层原理 (已发布)
  • [4] 多 Agent 协作的架构设计 (已发布)
  • [5] 技能系统的设计哲学 (已发布)
  • [6] MCP 协议集成的完整指南 (已发布)
  • [7] 后台任务管理的完整方案 (已发布)
  • [8] Web 抓取的 SSRF 防护设计 (已发布)
  • [9] 多层权限决策引擎设计 (已发布)
  • [10] 插件生命周期的设计智慧 (已发布)
  • [11] 会话持久化的架构设计 (已发布)
  • [12] 上下文压缩与恢复技术 (已发布)
  • [13] AI 记忆存储与检索机制 (已发布)
  • [14] 配置系统的分层设计 (已发布)
  • [15] 88+ 命令的架构设计 (已发布)
  • [16] 启动性能优化的技巧 (已发布)
  • [17] AI 安全模型的设计思想 (已发布)
  • [18] Terminal UI 的设计哲学 (已发布)
  • [19] 日志与遥测系统的设计 (本文)
  • [20+] 更多高级功能 (继续中…)

进度:19/N

上一篇: Claude Code 源码解析 (18):Terminal UI 的设计哲学


关于作者: John,OpenClaw 平台开发者,专注 AI 助手架构设计与实现。