skillhub/docs/observability-developer-guide.md
XiaoSeS 5058cc3387 feat(observability): propagate message trace context
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
2026-08-03 14:46:03 +08:00

150 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 可观测性开发者接入指南
本文说明 SkillHub 代码如何接入统一的日志关联和链路追踪标准。
开发者不需要直接操作 MDC、OpenTelemetry SDK 或 SkyWalking API。
## 1. 统一标准
| 信息 | 来源 | 日志字段 | 传播方式 |
|---|---|---|---|
| 请求关联 ID | `RequestIdFilter` / `RequestIdAccessor` | `request.id` | `X-Request-Id` |
| 分布式 Trace ID | Micrometer Tracing | `trace.id` | W3C `traceparent` |
| Span ID | Micrometer Tracing | `span.id` | 当前 Trace Scope |
`request.id` 是 SkillHub 的请求/审计关联标识,不等同于 `trace.id`
请求没有链路追踪时仍应保留 `request.id`
## 2. 运行模式
通过 `SKILLHUB_TRACING_MODE` 选择一种模式,修改后重启应用:
- `none`:默认模式。无应用内 OTel SDK 和 OTLP 导出,只保留 `request.id`
- `otel-sdk`:使用 Micrometer Tracing + OTel Bridge配置
`MANAGEMENT_OTLP_TRACING_ENDPOINT` 后才向 Collector 导出。
- `external-agent`:应用内 Tracer 为 NOOP由部署环境提供唯一的外部 Agent。
SkillHub 只能校验自身配置,不能识别任意 JVM Agent唯一 Agent 是部署检查项。
`none`/`external-agent` 不能配置 OTLP endpoint`otel-sdk` 与外部 Tracing Agent
不得在同一进程中叠加。
## 3. 开发者接入方式
### 3.1 普通 HTTP 请求
不需要增加代码。`RequestIdFilter` 会生成或校验 `X-Request-Id`,并在请求结束时清理
线程上下文。Micrometer Tracing 负责在 `otel-sdk` 模式下创建 HTTP Observation 和 Trace。
业务代码不要:
- `MDC.put` / `MDC.remove` 写入请求关联字段;
- 手工解析或拼接 `traceparent`
- 在日志中输出完整 MDC Map。
### 3.2 Spring 异步任务
优先使用已有的 `skillhubEventExecutor`
```java
@Async("skillhubEventExecutor")
public void handleEvent(SkillPublishedEvent event) {
// 直接记录日志即可request.id/trace.id/span.id 会按提交时的上下文恢复
}
```
新增 Spring 管理的线程池时,注入统一的
`ContextPropagatingTaskDecorator`,不要自己复制 MDC
```java
@Bean
ThreadPoolTaskExecutor myExecutor(
ContextPropagatingTaskDecorator contextDecorator
) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(contextDecorator);
executor.initialize();
return executor;
}
```
该装饰器负责捕获、恢复和清理 `RequestIdAccessor` 与 OTel Observation Scope。
### 3.3 内部 HTTP 服务
内部服务调用必须使用 Spring 管理的 `WebClient.Builder`,这样 `otel-sdk` 模式下会
自动传播 W3C Trace Context
```java
@Bean
HttpClient scannerClient(
WebClient.Builder builder
) {
return new WebClientHttpClient(builder.build());
}
```
Scanner 是当前已接入的内部客户端。新增内部客户端时,应补一个测试,断言请求包含合法
`traceparent`
### 3.4 外部 HTTP 服务
面向用户配置的 GitLab、第三方 API 等外部服务不要复用内部观测 Builder也不要手工
删除 Header。使用明确不接入 SkillHub Observation 的客户端,并补测试断言请求不包含
`traceparent`
### 3.5 Redis Stream 和定时任务
Redis Stream 已通过 `MessageObservationSupport` 接入通用消息传播:
- Producer 把 `traceparent``tracestate` 和受控的 `skillhub.request_id` 写入 Stream
transport metadata不修改 `ScanTask` 等业务对象;
- `AbstractStreamConsumer` 逐条提取上下文并建立 `CONSUMER` Observation`finally`
中恢复线程原状态;
- Consumer 内部调用 Scanner 时Spring 管理的 `WebClient` 自动创建同一 Trace 的子 Span
- 重试发布发生在当前 Consumer Scope 内新消息继续携带关联上下文Reclaimer 处理原消息
时重新从消息提取,不继承 Reclaimer 线程的上下文;
- `none``external-agent` 模式仍传播 Request ID应用保证完整 W3C Trace 的模式是
`otel-sdk`,外部 Agent 的跨 Stream Trace 能力取决于对应 Agent 插件。
新增 Redis Stream Consumer 应继承 `AbstractStreamConsumer`,新增 Producer 应调用
`MessageObservationSupport.observePublish`。其他消息中间件只实现自身 carrier 的
`MessageCarrierAdapter`;传播核心不依赖 Redis、Redisson 或 `Map`。不要在业务 DTO、MDC
或日志代码中复制上下文。
普通 `@Scheduled` 任务没有上游消息 carrier仍是独立后台边界需要长期任务关联时应使用
稳定任务 ID而不是把任意历史 HTTP Span 保持为超长父 Span。
## 4. 可扩展点
| 扩展需求 | 应扩展的位置 | 不应修改的位置 |
|---|---|---|
| 新增请求关联来源 | `RequestIdFilter` / `RequestIdAccessor` | 业务 Controller、DTO |
| 新增线程上下文 | `RequestIdThreadLocalAccessor` / `ContextRegistry` | 每个任务的 `MDC` 代码 |
| 新增 Tracing 后端 | Micrometer Bridge / Collector 配置 | 业务服务 |
| 新增日志字段 | `SkillHubEcsEncoder` 白名单 | “输出全部 MDC” |
| 新增内部 HTTP 客户端 | Spring `WebClient.Builder` + propagation test | URL 正则删 Header |
| 新增外部 HTTP 客户端 | 独立客户端构建入口 + no-propagation test | 依赖全局默认行为 |
| 新增消息队列边界 | `MessageObservationSupport` + `MessageCarrierAdapter` | 业务 DTO、手工 MDC/OTel API |
## 5. 接入验收清单
新增一个执行边界或客户端时,至少补充:
1. `none` 模式下业务结果不变;
2. `otel-sdk` 模式下内部调用的 `traceparent` 合法;
3. 外部调用不携带 `traceparent`
4. 线程复用后上下文被清理,不发生串号;
5. 日志只出现 `request.id``trace.id``span.id` 等白名单字段;
6. Collector 不可用时不影响业务结果。
7. 消息 Producer/Consumer 使用同一 TraceRequest ID 不串号,重试和 Reclaimer 不丢关联。
运行后端验证使用:
```bash
make test-backend-app
```
部署级变更再运行:
```bash
make staging
```