All checks were successful
Build and Deploy Log Center / build-and-deploy (push) Successful in 1m30s
300 lines
7.2 KiB
Markdown
300 lines
7.2 KiB
Markdown
# Log Center 接入指南
|
||
|
||
## 概述
|
||
|
||
Log Center 是一个集中式错误日志收集平台,提供 REST API 供各项目接入,实现运行时错误的统一收集、去重、追踪和分析。
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
### 服务地址
|
||
|
||
| 环境 | API 地址 | 仪表盘 |
|
||
|------|----------|--------|
|
||
| 本地开发 | `http://localhost:8002` | `http://localhost:8003` |
|
||
| 生产环境 | `https://log.yourcompany.com` | `https://log.yourcompany.com` |
|
||
|
||
---
|
||
|
||
## API 接口
|
||
|
||
### 上报错误日志
|
||
|
||
**POST** `/api/v1/logs/report`
|
||
|
||
#### 请求体 (JSON)
|
||
|
||
```json
|
||
{
|
||
"project_id": "rtc_backend",
|
||
"environment": "production",
|
||
"level": "ERROR",
|
||
"timestamp": "2026-01-30T10:30:00Z",
|
||
"version": "1.2.3",
|
||
"commit_hash": "abc1234",
|
||
"error": {
|
||
"type": "ValueError",
|
||
"message": "invalid literal for int() with base 10: 'abc'",
|
||
"file_path": "apps/users/views.py",
|
||
"line_number": 42,
|
||
"stack_trace": [
|
||
"Traceback (most recent call last):",
|
||
" File \"apps/users/views.py\", line 42, in get_user",
|
||
" user_id = int(request.GET['id'])",
|
||
"ValueError: invalid literal for int() with base 10: 'abc'"
|
||
]
|
||
},
|
||
"context": {
|
||
"url": "/api/users/123",
|
||
"method": "GET",
|
||
"user_id": "u_12345",
|
||
"request_id": "req_abc123"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 字段说明
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `project_id` | string | ✅ | 项目标识,如 `rtc_backend`, `rtc_web` |
|
||
| `environment` | string | ✅ | 环境:`development`, `staging`, `production` |
|
||
| `level` | string | ✅ | 日志级别:`ERROR`, `WARNING`, `CRITICAL` |
|
||
| `timestamp` | string | ❌ | ISO 8601 格式,不传则使用服务器时间 |
|
||
| `version` | string | ❌ | 应用版本号 |
|
||
| `commit_hash` | string | ❌ | Git commit hash |
|
||
| `error.type` | string | ✅ | 异常类型,如 `ValueError`, `TypeError` |
|
||
| `error.message` | string | ✅ | 错误消息 |
|
||
| `error.file_path` | string | ✅ | 出错文件路径 |
|
||
| `error.line_number` | int | ✅ | 出错行号 |
|
||
| `error.stack_trace` | array | ✅ | 堆栈信息(数组或字符串) |
|
||
| `context` | object | ❌ | 额外上下文信息(URL、用户ID等) |
|
||
|
||
#### 响应
|
||
|
||
**成功 (200)**
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"id": 123,
|
||
"fingerprint": "a1b2c3d4e5f6",
|
||
"is_new": true
|
||
}
|
||
```
|
||
|
||
**已存在 (200)** - 重复错误自动去重
|
||
```json
|
||
{
|
||
"status": "duplicate",
|
||
"id": 123,
|
||
"fingerprint": "a1b2c3d4e5f6",
|
||
"is_new": false
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 接入示例
|
||
|
||
### Python (Django / FastAPI)
|
||
|
||
```python
|
||
import requests
|
||
import traceback
|
||
import os
|
||
|
||
LOG_CENTER_URL = os.getenv("LOG_CENTER_URL", "http://localhost:8002")
|
||
|
||
def report_error(exc, context=None):
|
||
"""上报错误到 Log Center"""
|
||
tb = traceback.extract_tb(exc.__traceback__)
|
||
last_frame = tb[-1] if tb else None
|
||
|
||
payload = {
|
||
"project_id": "rtc_backend",
|
||
"environment": os.getenv("ENVIRONMENT", "development"),
|
||
"level": "ERROR",
|
||
"error": {
|
||
"type": type(exc).__name__,
|
||
"message": str(exc),
|
||
"file_path": last_frame.filename if last_frame else "unknown",
|
||
"line_number": last_frame.lineno if last_frame else 0,
|
||
"stack_trace": traceback.format_exception(exc)
|
||
},
|
||
"context": context or {}
|
||
}
|
||
|
||
try:
|
||
requests.post(
|
||
f"{LOG_CENTER_URL}/api/v1/logs/report",
|
||
json=payload,
|
||
timeout=3 # 快速失败,不影响主业务
|
||
)
|
||
except Exception:
|
||
pass # 静默失败,不影响主业务
|
||
```
|
||
|
||
#### Django 集成位置
|
||
|
||
修改 `utils/exceptions.py` 的 `custom_exception_handler`:
|
||
|
||
```python
|
||
def custom_exception_handler(exc, context):
|
||
# 上报到 Log Center (异步,不阻塞响应)
|
||
report_error(exc, {
|
||
"view": str(context.get("view")),
|
||
"request_path": context.get("request").path if context.get("request") else None,
|
||
})
|
||
|
||
# ... 原有逻辑不变 ...
|
||
```
|
||
|
||
---
|
||
|
||
### JavaScript / TypeScript (React / Vue)
|
||
|
||
```typescript
|
||
const LOG_CENTER_URL = import.meta.env.VITE_LOG_CENTER_URL || 'http://localhost:8002';
|
||
|
||
interface ErrorPayload {
|
||
project_id: string;
|
||
environment: string;
|
||
level: string;
|
||
error: {
|
||
type: string;
|
||
message: string;
|
||
file_path: string;
|
||
line_number: number;
|
||
stack_trace: string[];
|
||
};
|
||
context?: Record<string, unknown>;
|
||
}
|
||
|
||
export function reportError(error: Error, context?: Record<string, unknown>) {
|
||
// 解析堆栈信息
|
||
const stackLines = error.stack?.split('\n') || [];
|
||
const match = stackLines[1]?.match(/at\s+.*\s+\((.+):(\d+):\d+\)/);
|
||
|
||
const payload: ErrorPayload = {
|
||
project_id: 'rtc_web',
|
||
environment: import.meta.env.MODE,
|
||
level: 'ERROR',
|
||
error: {
|
||
type: error.name,
|
||
message: error.message,
|
||
file_path: match?.[1] || 'unknown',
|
||
line_number: parseInt(match?.[2] || '0'),
|
||
stack_trace: stackLines,
|
||
},
|
||
context: {
|
||
url: window.location.href,
|
||
userAgent: navigator.userAgent,
|
||
...context,
|
||
},
|
||
};
|
||
|
||
// 使用 sendBeacon 确保页面关闭时也能发送
|
||
if (navigator.sendBeacon) {
|
||
navigator.sendBeacon(
|
||
`${LOG_CENTER_URL}/api/v1/logs/report`,
|
||
JSON.stringify(payload)
|
||
);
|
||
} else {
|
||
fetch(`${LOG_CENTER_URL}/api/v1/logs/report`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload),
|
||
keepalive: true,
|
||
}).catch(() => {});
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Axios 拦截器集成
|
||
|
||
修改 `src/api/request.ts`:
|
||
|
||
```typescript
|
||
request.interceptors.response.use(
|
||
(response) => { /* ... */ },
|
||
(error: AxiosError) => {
|
||
// 上报到 Log Center
|
||
reportError(error, {
|
||
url: error.config?.url,
|
||
method: error.config?.method,
|
||
status: error.response?.status,
|
||
});
|
||
|
||
// ... 原有逻辑不变 ...
|
||
}
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 错误去重机制
|
||
|
||
Log Center 使用 **指纹(fingerprint)** 对错误进行去重:
|
||
|
||
```
|
||
fingerprint = MD5(project_id + error_type + file_path + line_number)
|
||
```
|
||
|
||
相同指纹的错误只会记录一次,后续只更新计数和最后出现时间。
|
||
|
||
---
|
||
|
||
## 错误状态流转
|
||
|
||
```
|
||
NEW → VERIFYING → PENDING_FIX → FIXING → FIXED → VERIFIED → DEPLOYED
|
||
↓ ↓
|
||
CANNOT_REPRODUCE FIX_FAILED
|
||
```
|
||
|
||
| 状态 | 说明 |
|
||
|------|------|
|
||
| `NEW` | 新上报的错误 |
|
||
| `VERIFYING` | 正在验证复现 |
|
||
| `CANNOT_REPRODUCE` | 无法复现 |
|
||
| `PENDING_FIX` | 等待修复 |
|
||
| `FIXING` | 正在修复中 |
|
||
| `FIXED` | 已修复,待验证 |
|
||
| `VERIFIED` | 已验证修复 |
|
||
| `DEPLOYED` | 已部署上线 |
|
||
| `FIX_FAILED` | 修复失败 |
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
1. **设置超时**: 上报请求设置 3 秒超时,避免影响主业务
|
||
2. **静默失败**: 上报失败不应影响用户体验
|
||
3. **异步上报**: 使用异步方式上报,不阻塞主流程
|
||
4. **添加上下文**: 尽量添加有用的上下文信息(用户ID、请求URL等)
|
||
5. **环境区分**: 正确设置 `environment` 字段区分开发/生产
|
||
|
||
---
|
||
|
||
## 环境变量配置
|
||
|
||
### Python 项目
|
||
```bash
|
||
# .env
|
||
LOG_CENTER_URL=http://localhost:8002
|
||
ENVIRONMENT=development
|
||
```
|
||
|
||
### JavaScript 项目
|
||
```bash
|
||
# .env
|
||
VITE_LOG_CENTER_URL=http://localhost:8002
|
||
```
|
||
|
||
---
|
||
|
||
## API 文档
|
||
|
||
完整 API 文档请访问: [http://localhost:8002/docs](http://localhost:8002/docs)
|