8.9 KiB
8.9 KiB
name, overview, todos
| name | overview | todos | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| OpenTelemetry三信号完整接入 | 实现 OpenTelemetry Traces/Metrics/Logs 三大信号的完整初始化与 OTLP gRPC 导出,补齐当前 main.go 引用的未实现桩(NewOTelProvider/OTelShutdown),保持本地日志不变的同时新增 OTel 导出,并加优雅关闭以刷新数据。 |
|
产品概述
为「衣柜/穿搭」后端补齐 OpenTelemetry 三大信号(Traces / Metrics / Logs)的完整初始化与 OTLP gRPC 导出,把当前"只埋点、未导出"的半接入状态升级为可真正观测的完整链路,并修复因待实现桩导致的编译失败问题。
核心功能
- Traces(链路追踪):注册真实 TracerProvider(Resource + OTLP gRPC 批量导出 + 采样率),使全项目已有的 span 与 gin 请求 span 真正导出。
- Metrics(指标):注册 MeterProvider(OTLP gRPC 周期导出),采集 Go runtime 指标,并预留自定义指标入口。
- Logs(日志):新增 OTel 日志导出(otelslog 桥接),同时保留现有本地 JSON 日志(文件+stdout,含 trace_id/span_id 注入)。
- 优雅关闭:进程收到退出信号时先关闭 HTTP 服务,再刷新并关闭三个 Provider,避免丢失缓冲数据。
- 开关与降级:尊重配置
Enabled开关;未启用或采集器不可用时降级为 no-op,服务照常启动、业务不受影响、不 panic。
技术栈
- 语言/框架:Go 1.27 + Gin(沿用现有项目),OpenTelemetry-Go SDK。
- 基准版本对齐现有
go.opentelemetry.io/otel v1.46.0;SDK trace/metric 用对应 v1.46.x,log 相关为配套 v0.x,由go mod tidy解析。 - 传输:OTLP over gRPC,导出到配置的
ExporterEndpoint(如127.0.0.1:4317)。
实现思路
在 pkg/tracing 新增统一初始化入口 NewOTelProvider,一次性构建 Resource 与三个 SDK Provider(Tracer/Meter/Logger),设置到 otel 全局,并返回聚合的 shutdown 函数;main.go 修正现有待实现桩(setupOTel 引用的未定义符号)、修正 init 顺序与错误降级、补充信号监听与优雅关闭。业务层 100+ 处 global.Tracer.Start(...) 与 otelgin.Middleware(...) 无需改动,Provider 注册后立即生效。
关键技术决策
- 统一入口 + 聚合 Shutdown:单一
NewOTelProvider返回(trace.Tracer, func(ctx) error, error),内部聚合三个 Provider 的 Shutdown,main只持有一个global.OTelShutdown,降低接线复杂度与遗漏 flush 风险。 - 日志扇出(fanout)而非替换:保留现有本地 JSON handler(含
OtelContextHandler的 trace 注入),叠加otelslogbridge handler,用一个极简自研slog.Handler扇出到两者,避免引入第三方 slog-multi 依赖,且不破坏现有日志落盘。 - 降级优先,保障可用性:
Enabled=false或 exporter 建立失败时,global.Tracer使用 no-op tracer(业务层Tracer.Start安全非 nil),OTelShutdown为空实现;Insecure=true用非 TLS gRPC(内网采集器语义,不额外放开外网)。 - 批处理与性能:Trace 用 BatchSpanProcessor、Metric 用 PeriodicReader、Log 用 BatchProcessor,均为异步批量导出,热路径开销可控;采样由
SamplerRatio控制(ParentBased(TraceIDRatioBased)),对 <=0 做默认兜底 1.0。
实现要点(防回归)
- 编译阻断修复:
main.go现引用未定义符号tracing.NewOTelProvider、global.OTelShutdown、global.OTelSetting(真实名为global.OtelSetting)。需实现前两者、并把 setupOTel 内改回global.OtelSetting,使项目恢复可编译。 - 配置键名对齐 bug:
config.example.yaml使用SampleRatio,而setting.OtelSettingS字段为SamplerRatio(拼写不一致 → viper 读不到 → 采样率 0 → 不采样)。统一为SamplerRatio(改 yaml 键),并在代码对 ratio<=0 兜底 1.0。同时configs/config.yaml(实际运行配置)若存在同样键名需一并对齐。 - init 顺序:需保证 setupOTel 在 setupSlogLogger 之前完成 LoggerProvider 构建,slog 扇出才能拿到 bridge;或 setupSlogLogger 在 provider 就绪后重建默认 logger。
- 错误处理:setupOTel 失败时告警不 panic(保持服务可启动),并降级 no-op。
- 优雅关闭:
main()当前直接ListenAndServe,需改为监听 SIGINT/SIGTERM,先http.Server.Shutdown(ctx)再global.OTelShutdown(ctx),设置合理超时(如 5s)避免卡死。 - 复用现有
internal/middleware/otel.go(otelgin)与pkg/tracing/otel.go(OtelContextHandler),不重复造轮子。
架构与数据流
flowchart TD
A[HTTP 请求] --> B[otelgin 中间件 server span]
B --> C[handler/service/dao<br/>global.Tracer.Start 子 span]
C --> D[slog 扇出 Handler]
D --> E[本地 JSON 文件+stdout<br/>OtelContextHandler 注入 trace_id]
D --> F[otelslog bridge]
C --> G[TracerProvider<br/>BatchSpanProcessor]
C --> H[MeterProvider<br/>PeriodicReader + runtime 指标]
F --> I[LoggerProvider<br/>BatchProcessor]
G --> J[(OTLP gRPC Collector<br/>ExporterEndpoint)]
H --> J
I --> J
目录结构
zt-closet-backend/
├── go.mod / go.sum # [MODIFY] go mod tidy 补 otel sdk / sdk-metric / sdk-log / resource+semconv /
│ # exporters/otlp/{otlptracegrpc, otlpmetricgrpc, otlploggrpc} /
│ # contrib/instrumentation/runtime / contrib/bridges/otelslog / log/global
├── global/
│ └── otel.go # [MODIFY] 保留 Tracer;新增 OTelShutdown func(context.Context) error、
│ # Meter metric.Meter(可选自定义指标入口)、LoggerProvider(可选)
├── pkg/
│ ├── setting/section.go # [MODIFY] 若选择改字段则同步;默认优先改 yaml 对齐字段(本方案改 yaml,section 不动)
│ └── tracing/
│ ├── otel.go # [KEEP] OtelContextHandler 保留
│ ├── provider.go # [NEW] NewOTelProvider:构建 Resource + Tracer/Meter/Logger Provider +
│ │ # OTLP gRPC 导出 + runtime 指标 + 采样 + Enabled/Insecure 开关 +
│ │ # no-op 降级 + 聚合 shutdown
│ └── fanout.go # [NEW] fanoutHandler:极简 slog.Handler,将日志同时发给本地 handler 与 otelslog bridge
├── internal/middleware/
│ └── otel.go # [KEEP] otelgin 中间件,无需改
├── configs/
│ ├── config.example.yaml # [MODIFY] OTel 段键名对齐 SamplerRatio,校正注释
│ └── config.yaml # [MODIFY] 若存在同名键,一并对齐 SamplerRatio(运行时实际读取)
└── main.go # [MODIFY] 修正 setupOTel 符号与错误降级;调整 init 顺序;
# setupSlogLogger 使用扇出 handler;main() 加信号监听 + 优雅关闭
关键代码结构(接口约定)
// pkg/tracing/provider.go —— 统一初始化入口
// 返回:全局 Tracer、聚合 Shutdown(flush 三个 Provider)、错误
// Enabled=false 或初始化失败时返回 no-op Tracer + 安全空 Shutdown
func NewOTelProvider(cfg *setting.OtelSettingS) (trace.Tracer, func(context.Context) error, error)