ProductReadyProductReady

请求日志与监控

使用 OpenTelemetry 和 OpenObserve 追踪所有 HTTP 请求,用于审计、分析和性能监控

请求日志与监控

ProductReady 内置了基于 OpenTelemetryOpenObserve 的中心化请求日志系统。追踪每个 HTTP 请求(API 调用、页面访问、tRPC、OpenAPI),用于审计跟踪、分析和性能监控。

注重隐私:仅记录元数据(方法、路径、状态、耗时)— 不记录请求体或敏感数据。


快速开始

启动 OpenObserve

# 启动所有服务(包括 OpenObserve)
make db
# 或: docker-compose up -d

# 验证运行状态
curl http://localhost:5080/healthz
# {"status":"ok"}

启用请求日志

.env 文件中:

# 启用请求日志(默认禁用)
ENABLE_REQUEST_LOGGING="true"

# OpenObserve 端点
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:5080/api/default"

# serviceName 自动从 appConfig.name 获取,version 从 package.json 读取

# OpenObserve 认证
OPENOBSERVE_EMAIL="admin@productready.dev"
OPENOBSERVE_PASSWORD="productready"

重启应用

pnpm dev

# 你应该看到:
# [OTelLogger] ✅ Initialized (serviceName=productready)

在 OpenObserve 中查看日志

  1. 打开:http://localhost:5080
  2. 登录:admin@productready.dev / productready
  3. 导航到:LogsSearch
  4. 选择 stream:productready_request_logs

记录什么数据?

每个请求记录:

  • 用户 ID(已认证)或 "anonymous"
  • 会话 ID(来自 Better Auth)
  • IP 地址(从 Cloudflare/Vercel 头提取)
  • User Agent(浏览器/设备信息)
  • HTTP 方法(GET, POST, PUT, DELETE 等)
  • URL 路径/api/users/123
  • 状态码(200, 404, 500 等)
  • 耗时(响应时间,毫秒)
  • 时间戳(ISO 8601 格式)

自动排除的路径

  • 静态资源:/_next/, *.ico, *.png, *.css, *.js
  • 健康检查:/health, /ping
  • 认证回调:/api/auth/callback/*

OpenObserve 字段名映射

OpenObserve v0.14.0 将点分隔的 JSON 键名展平为下划线。查询时请使用展平后的名称。

JSON 键名(日志发送)OpenObserve 列名(查询使用)
http.request.methodhttp_request_method
http.response.status_codehttp_response_status_code
url.pathurl_path
http.duration_mshttp_duration_ms
user.iduser_id
user.session_iduser_session_id
client.addressclient_address
user_agent.originaluser_agent_original
service.nameservice_name

在 OpenObserve 中查询日志

基础查询

http_response_status_code > 0

显示所有已记录的请求及其状态码。

http_response_status_code >= 500

筛选服务器错误(5xx 状态码)。

http_duration_ms > 1000

查找耗时超过 1 秒的请求。

user_id = 'user_abc123'

追踪特定用户的所有请求。

高级查询

特定端点的请求:

url_path LIKE '/api/users/%'

匿名 vs 已认证:

user_id = 'anonymous'  -- 匿名用户
user_id != 'anonymous' -- 已认证用户

按 IP 地址筛选:

client_address = '203.0.113.42'

按时间筛选:

@timestamp > now() - 1h  -- 最近 1 小时
@timestamp > now() - 24h -- 最近 24 小时

架构

┌─────────────┐
│    请求     │
└──────┬──────┘


┌─────────────────────┐
│  Next.js 中间件     │  (proxy.ts → withRequestLogging)
└──────┬──────────────┘

   ┌───┴───┐
   │       │
   ▼       ▼
Traces    Logs
(OTel)  (HTTP POST)
   │       │
   ▼       ▼
┌─────────────────────┐
│    OpenObserve      │  http://localhost:5080
│   (OLAP 数据库)     │  Traces + Logs + 仪表板
└─────────────────────┘

双管道架构:

  • Traces(OTel SDK → OTLP):分布式追踪,性能 spans
  • Logs(直接 HTTP → /_json):结构化请求审计日志,可搜索

主要优势:

  • 非阻塞:日志被缓冲并异步发送(<1ms 影响)
  • 可扩展:支持 10 万+ 请求/天
  • 灵活:可切换到 Grafana Tempo、Jaeger 或任何 OTLP 端点
  • 成本效益:OLAP 存储比 PostgreSQL 便宜

配置

环境变量

变量描述默认值必需
ENABLE_REQUEST_LOGGING启用/禁用日志false
OTEL_EXPORTER_OTLP_ENDPOINTOpenObserve 端点http://localhost:5080/api/default
OPENOBSERVE_EMAILOpenObserve 登录邮箱admin@productready.dev
OPENOBSERVE_PASSWORDOpenObserve 密码productready
OPENOBSERVE_ORG_ID组织 IDdefault

注意: serviceName 自动从 appConfig.name 获取,serviceVersionpackage.json 读取 — 无需额外环境变量。


性能影响

基准测试

指标数值
日志开销<1ms/请求(缓冲)
最大吞吐量10万+ 请求/天
内存使用~50MB(批量缓冲)
日志批量刷新20 条或 5 秒间隔
Trace 批量刷新512 spans 或 5 秒间隔

故障排查

检查 1:日志是否启用?

echo $ENABLE_REQUEST_LOGGING
# 应输出:true

检查 2:OpenObserve 是否运行?

curl http://localhost:5080/healthz
# 应返回:{"status":"ok"}

检查 3:控制台日志

pnpm dev
# 应看到:[OTelLogger] Initialized with endpoint: ...

检查 4:发送测试请求

curl http://localhost:3000/
# 然后检查 OpenObserve UI

otel-logger.ts减小批量大小

maxQueueSize: 512,        // 从 2048 减少
maxExportBatchSize: 128,  // 从 512 减少

为高流量应用启用采样

// 仅记录 10% 的请求
if (Math.random() > 0.1) return;

错误:ECONNREFUSED

OpenObserve 未运行。使用以下命令启动:
make db  # 或: docker-compose up -d

错误:401 Unauthorized

检查 .env 中的 OPENOBSERVE_EMAIL 和 OPENOBSERVE_PASSWORD

错误:'Search field not found'

OpenObserve 会将点分隔的键名展平为下划线。
查询时使用 user_id 而非 "user.id"。
在 OpenObserve UI → stream schema 中查看实际字段名。

隐私与合规

GDPR 合规

请求日志是可选的(默认禁用)。为符合 GDPR:

  1. 告知用户:在隐私政策中说明
  2. 保留策略:30-90 天后删除日志
  3. 删除权利:提供端点删除用户日志
  4. 匿名化 IP:记录前哈希 IP 地址(可选)

数据最小化

仅记录必要的元数据:

  • ❌ 不记录请求体
  • ❌ 不记录响应体
  • ❌ 不记录 cookies(会话 ID 除外)
  • ❌ 不记录密码或 API 密钥
  • ✅ 仅记录:方法、路径、状态、耗时、用户 ID

相关文档

On this page