mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-08 03:07:51 +00:00
chore(integration): stage observability boundary fixes
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
commit
7f5d5466ff
7 changed files with 171 additions and 19 deletions
|
|
@ -56,7 +56,7 @@ SESSION_COOKIE_SECURE=false
|
|||
# Observability defaults require no Collector or tracing backend.
|
||||
# Use json in container deployments when stdout is collected centrally.
|
||||
SKILLHUB_TRACING_MODE=none
|
||||
SKILLHUB_LOG_FORMAT=text
|
||||
SKILLHUB_LOG_FORMAT=json
|
||||
SKILLHUB_LOG_ASYNC_QUEUE_SIZE=1024
|
||||
SKILLHUB_SERVICE_VERSION=unknown
|
||||
SKILLHUB_SERVICE_ENVIRONMENT=production
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ services:
|
|||
SKILLHUB_AUTH_CAS_ATTRIBUTES_EMAIL: ${SKILLHUB_AUTH_CAS_ATTRIBUTES_EMAIL:-}
|
||||
SKILLHUB_AUTH_CAS_ATTRIBUTES_AVATAR_URL: ${SKILLHUB_AUTH_CAS_ATTRIBUTES_AVATAR_URL:-}
|
||||
SKILLHUB_TRACING_MODE: ${SKILLHUB_TRACING_MODE:-none}
|
||||
SKILLHUB_LOG_FORMAT: ${SKILLHUB_LOG_FORMAT:-text}
|
||||
SKILLHUB_LOG_FORMAT: ${SKILLHUB_LOG_FORMAT:-json}
|
||||
SKILLHUB_LOG_ASYNC_QUEUE_SIZE: ${SKILLHUB_LOG_ASYNC_QUEUE_SIZE:-1024}
|
||||
SKILLHUB_SERVICE_VERSION: ${SKILLHUB_SERVICE_VERSION:-unknown}
|
||||
SKILLHUB_SERVICE_ENVIRONMENT: ${SKILLHUB_SERVICE_ENVIRONMENT:-production}
|
||||
|
|
|
|||
|
|
@ -331,13 +331,14 @@ override 或部署平台环境变量把上述 `SPRING_SECURITY_*` 变量注入 `
|
|||
|
||||
```dotenv
|
||||
SKILLHUB_TRACING_MODE=none
|
||||
SKILLHUB_LOG_FORMAT=text
|
||||
SKILLHUB_LOG_FORMAT=json
|
||||
SKILLHUB_SERVICE_VERSION=v0.2.15
|
||||
SKILLHUB_SERVICE_ENVIRONMENT=production
|
||||
```
|
||||
|
||||
部署环境建议将 `SKILLHUB_LOG_FORMAT` 设为 `json`,由 Filebeat、Fluent Bit 或容器平台
|
||||
采集 stdout。SkillHub 不直接连接 Elasticsearch。JSON 日志使用以下稳定字段:
|
||||
发布 Compose 默认使用 ECS 风格 JSON,由 Filebeat、Fluent Bit 或容器平台采集 stdout。
|
||||
本地源码开发仍可使用 `SKILLHUB_LOG_FORMAT=text`。SkillHub 不直接连接 Elasticsearch。
|
||||
JSON 日志使用以下稳定字段:
|
||||
|
||||
- `request.id`:SkillHub 请求、响应和审计关联 ID。
|
||||
- `trace.id`、`span.id`:当前存在有效 Trace 时输出。
|
||||
|
|
@ -447,6 +448,9 @@ SkyWalking 中没有被保留的 Trace。这是头部采样的预期行为。
|
|||
|
||||
关闭 Trace 和 JSON 日志不会改变请求、数据库或异步任务的业务语义。
|
||||
|
||||
开发者接入统一标准的最小步骤、内部/外部 HTTP Client 传播边界和扩展点见:
|
||||
[可观测性开发者接入指南](./observability-developer-guide.md)。
|
||||
|
||||
## 11 安全扫描服务
|
||||
|
||||
如果要启用 `skill-scanner` 后端链路,当前仓库建议按下面的方式部署:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ Issue #597 中“搜索索引可靠异步交付”应作为独立问题处理,
|
|||
Elasticsearch/Kibana。
|
||||
7. Trace 可以选择通过 OTLP Collector 接入 SkyWalking。
|
||||
8. Collector、SkyWalking、Elasticsearch 或日志采集器不可用时,SkillHub 业务继续运行。
|
||||
9. 同一进程只能有一个实际生效的 Tracer。
|
||||
9. SkillHub 应用配置只能启用一个应用内 Tracer;`external-agent` 模式下唯一外部
|
||||
Agent 由部署参数和发布检查保证。
|
||||
|
||||
本方案按多个小阶段、小提交实施和验证,全部通过后再统一创建一个替代 PR。
|
||||
|
||||
|
|
@ -104,14 +105,15 @@ skillhub:
|
|||
必须保证:
|
||||
|
||||
- `none` 和 `external-agent` 不创建应用内 OTel Span。
|
||||
- `otel-sdk` 不支持同时启用 SkyWalking、OTel 或其他外部 Tracing Agent。
|
||||
- `otel-sdk` 不支持同时启用 SkyWalking、OTel 或其他外部 Tracing Agent;应用只能校验
|
||||
自身 endpoint/mode 冲突,不能可靠识别任意 JVM Agent。
|
||||
- `external-agent` 不创建 OTLP Exporter。
|
||||
- SkillHub 配置能够识别的冲突应在启动时失败;任意 Java Agent 无法被应用可靠识别,因此
|
||||
部署检查和原型测试还必须验证实际 JVM 参数中只有一个 Tracer。
|
||||
|
||||
一期实现并验证三种模式的互斥边界和日志关联。`external-agent` 只验证 SkyWalking Agent
|
||||
接管 Trace 后不会与应用内 OTel Tracer 冲突;SkyWalking 特有高级能力不进入 SkillHub
|
||||
核心代码。
|
||||
一期实现并验证三种模式的应用上下文互斥边界和日志关联。`external-agent` 只验证
|
||||
SkyWalking Agent 接管 Trace 时应用内 OTel Tracer/Exporter 不工作;“只挂载一个外部
|
||||
Agent”属于部署验收项。SkyWalking 特有高级能力不进入 SkillHub 核心代码。
|
||||
|
||||
## 6. 关联字段契约
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
# 通用可观测性决策图
|
||||
|
||||
目标:为 SkillHub 建立独立、通用、可插拔的日志关联、指标和链路追踪基础设施。
|
||||
它观察 HTTP、线程池、定时任务和可靠任务等执行边界,但不进入业务模型和业务载荷。
|
||||
当前实现覆盖 HTTP、SkillHub 管理的线程池和明确接入的内部 HTTP Client;定时任务、
|
||||
Redis Stream 消费循环和 Reclaimer 是独立后台边界,不继承 HTTP 上下文。
|
||||
这些边界不进入业务模型和业务载荷。
|
||||
|
||||
边界:
|
||||
|
||||
- Servlet Filter、执行器装饰器、调度拦截器和任务执行拦截器负责建立/恢复上下文。
|
||||
- Servlet Filter、执行器装饰器和明确接入的 Client Builder 负责建立/恢复上下文。
|
||||
- 定时任务和 Redis Stream 目前只输出自身执行日志,不自动继承请求或 Trace 上下文。
|
||||
- 业务代码不读写 MDC,不负责创建通用 Span,也不负责统计任务生命周期指标。
|
||||
- 使用 W3C Trace Context;日志后端、Metrics 后端和 Trace Exporter 均可替换。
|
||||
- 上下文是有长度限制的基础设施元数据,不进入业务 payload。
|
||||
|
|
@ -13,8 +16,9 @@
|
|||
|
||||
必须满足的不变量:
|
||||
|
||||
- 每个执行边界都正确建立作用域并在 `finally` 清理,线程复用不得串号。
|
||||
- 日志稳定输出 `requestId`、`traceId`、`spanId`;任务执行时额外输出执行资源标识。
|
||||
- 每个已纳入本期的执行边界都正确建立作用域并在 `finally` 清理,线程复用不得串号。
|
||||
- 日志稳定输出 `requestId`、`traceId`、`spanId`(存在时);任务执行资源标识不由本期
|
||||
可观测性自动生成。
|
||||
- Trace 与 Metrics 可关闭、可替换;关闭后不得改变业务行为。
|
||||
- 指标只使用低基数维度,业务 ID 不进入标签。
|
||||
- 采集端不可用必须异步、限时、限队列并 fail-open。
|
||||
|
|
@ -40,8 +44,8 @@ Type: Research
|
|||
|
||||
### Question
|
||||
|
||||
如何区分现有 `X-Request-Id`、W3C `traceId/spanId` 和执行资源标识,并跨 HTTP、线程池、
|
||||
调度器和持久化任务边界传播?
|
||||
如何区分现有 `X-Request-Id`、W3C `traceId/spanId` 和执行资源标识,并跨 HTTP、线程池
|
||||
边界传播?
|
||||
|
||||
### Answer
|
||||
|
||||
|
|
@ -50,7 +54,7 @@ Type: Research
|
|||
- `requestId` 是 SkillHub 的请求/审计关联标识,不冒充分布式 Trace。
|
||||
- `traceId/spanId` 由 Tracer 生成,跨进程只使用 W3C `traceparent/tracestate`。
|
||||
- 定时任务或可靠任务的执行资源 ID 只作为当前执行作用域属性,不进入业务 payload。
|
||||
- HTTP、线程池、调度器和持久化 carrier 的注入/提取全部位于基础设施拦截器。
|
||||
- HTTP 和线程池 carrier 的注入/提取位于基础设施拦截器;持久化任务不携带 HTTP Trace。
|
||||
- 不传播任意 MDC Map;baggage 默认关闭,任何允许项都必须低敏、限长、显式配置。
|
||||
- 无效或不可信的公网 Trace Context 按 W3C 规则丢弃,服务端控制采样。
|
||||
|
||||
|
|
@ -84,8 +88,9 @@ Type: Prototype
|
|||
|
||||
### Question
|
||||
|
||||
验证线程复用隔离、嵌套作用域、异步/调度/持久化任务边界、采样、Exporter 超时、
|
||||
Collector 中断、队列打满和关闭观测能力等场景。
|
||||
验证线程复用隔离、嵌套作用域、异步任务边界、采样、Exporter 超时、Collector 中断、
|
||||
队列打满和关闭观测能力等场景。调度任务和 Redis Stream 的独立后台边界只验证不继承
|
||||
请求上下文。
|
||||
|
||||
### Answer
|
||||
|
||||
|
|
@ -97,6 +102,7 @@ Collector 中断、队列打满和关闭观测能力等场景。
|
|||
- Scanner 使用 Spring 管理的 `WebClient.Builder` 传播 W3C `traceparent`。
|
||||
- 面向用户配置的 GitLab 外部 Client 不传播 Trace Context。
|
||||
- `none / otel-sdk / external-agent` 的应用上下文和 Exporter 条件符合设计。
|
||||
- `@Scheduled` 和 Redis Stream/Reclaimer 不继承请求上下文,保持独立后台执行边界。
|
||||
|
||||
Collector 中断、日志背压、采样率和关闭行为仍由 `big-main` 精确 SHA 镜像的远端原型验证。
|
||||
|
||||
|
|
|
|||
138
docs/observability-developer-guide.md
Normal file
138
docs/observability-developer-guide.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# 可观测性开发者接入指南
|
||||
|
||||
本文说明 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 消费循环和 Reclaimer 视为独立后台执行边界:
|
||||
|
||||
- 不继承任意 HTTP 请求的 `request.id` 或 `trace.id`;
|
||||
- 不把 HTTP Trace Context 写入 Redis 业务载荷;
|
||||
- 日志仍可使用 ECS 格式和固定服务字段;
|
||||
- 若未来需要任务级关联,应增加独立的任务执行 ID/Observation carrier,并单独设计
|
||||
持久化与重试语义。
|
||||
|
||||
因此,不要假设在 `@Scheduled` 或 Stream consumer 中能自动查到发起 HTTP 请求的 Trace。
|
||||
|
||||
## 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 | 依赖全局默认行为 |
|
||||
|
||||
## 5. 接入验收清单
|
||||
|
||||
新增一个执行边界或客户端时,至少补充:
|
||||
|
||||
1. `none` 模式下业务结果不变;
|
||||
2. `otel-sdk` 模式下内部调用的 `traceparent` 合法;
|
||||
3. 外部调用不携带 `traceparent`;
|
||||
4. 线程复用后上下文被清理,不发生串号;
|
||||
5. 日志只出现 `request.id`、`trace.id`、`span.id` 等白名单字段;
|
||||
6. Collector 不可用时不影响业务结果。
|
||||
|
||||
运行后端验证使用:
|
||||
|
||||
```bash
|
||||
make test-backend-app
|
||||
```
|
||||
|
||||
部署级变更再运行:
|
||||
|
||||
```bash
|
||||
make staging
|
||||
```
|
||||
|
|
@ -8,6 +8,7 @@ import com.iflytek.skillhub.auth.identity.ProviderAuthenticationResult;
|
|||
import com.iflytek.skillhub.auth.identity.SubjectCandidate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
|
|
@ -38,6 +39,7 @@ public class GitLabClaimsExtractor implements OAuthClaimsExtractor {
|
|||
* Uses an external-service client that is intentionally not customized with application
|
||||
* tracing. Trace context must not be propagated to a user-configured GitLab host.
|
||||
*/
|
||||
@Autowired
|
||||
public GitLabClaimsExtractor() {
|
||||
this(RestClient.builder());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue