--- name: service-span-and-async-trace overview: 给 service 层所有对外入口方法补埋 span(svc.Xxx),并修复 clothes 上传异步 goroutine 的 trace 断链,使调用链可读、异步链路可关联。 todos: - id: clothes-spans content: 为 clothes.go 的 6 个入口方法(Upload、ProcessStatusGetByUUID、GetByUUID、UpdateByUUID、DeleteByUUID、List)补埋 svc span status: completed - id: clothes-async-trace content: 修复 clothes.go 异步断链:改造 processImageAsync 签名与 ctx 派生、补 svc.ProcessImageAsync span、更新调用点、新增 otel/trace import status: completed dependencies: - clothes-spans - id: outfit-closet-collect-like-spans content: 为 outfit.go、closet.go、collect.go、like.go 的入口方法补埋 svc span 并新增 global import status: completed - id: notification-quota-user-goods-horoscope-spans content: 为 notification.go、quota.go、user.go、goods.go、horoscope.go 的入口方法补埋 svc span 并补充所需 import status: completed - id: order-airecommend-spans content: 为 order.go、airecommend.go 的入口方法补埋 svc span 并新增 global import status: completed - id: build-vet-verify content: 运行 go build ./... 与 go vet ./... 验证编译与静态检查通过 status: completed dependencies: - clothes-spans - clothes-async-trace - outfit-closet-collect-like-spans - notification-quota-user-goods-horoscope-spans - order-airecommend-spans --- ## 用户需求 1. 在 service 层对外入口方法补埋 OpenTelemetry span,使 trace 调用链可读。 2. 修复衣物上传异步 goroutine 的 trace 断链,使抠图、COS 上传、分类、回填等异步 DAO span 能关联回原请求。 ## 功能内容 - 为 12 个 service 文件、共 41 个对外入口方法(首字母大写的导出方法)统一补埋 `svc.Xxx` span,命名与现有 `dao.Xxx`、`upload.Xxx` 前缀保持一致。 - 私有辅助方法不埋点,避免产生过深的 span 森林。 - 修复 `processImageAsync` 使用 `context.Background()` 导致异步链路丢失父 trace 的问题,通过 `trace.ContextWithSpanContext` 继承 trace 信息、同时切断取消信号。 - 异步分支补一个 `svc.ProcessImageAsync` span,将异步处理在 trace 中聚合成一个独立分组。 ## 视觉/可观测效果 - 在 trace 平台中,每个 HTTP 请求下能看到 `svc.Xxx` 容器 span,其下嵌套对应业务调用的 `dao.Xxx`、`upload.Xxx` span,调用层次清晰。 - 衣物上传后,后台异步处理(抠图、COS、分类、回填)不再生成孤立 trace,而是挂在原请求(或 `svc.ProcessImageAsync`)之下,便于端到端排查。 ## Tech Stack - 语言:Go 1.27 - 可观测:OpenTelemetry Trace(已接入,`global.Tracer`) - Web 框架:Gin(API 层已由 `otelgin.Middleware` 自动埋 server span,本次不涉及) ## Implementation Approach 采用「只补边界、不埋全程」的埋点策略: 1. service 入口方法方法体开头统一插入: ``` ctx, span := global.Tracer.Start(ctx, "svc.Xxx") defer span.End() ``` span 名 = `svc.` + 方法名。通过覆盖入口方法,使 DAO/upload 子 span 在 trace 中正确归属到业务动作。 2. 修复异步断链:将 `processImageAsync` 签名改为接收 `parentCtx context.Context`,内部用: ``` ctx, cancel := context.WithTimeout(trace.ContextWithSpanContext(context.Background(), trace.SpanContextFromContext(parentCtx)), 3*time.Minute) ``` 继承父 trace 的 span context,但用 `context.Background()` 切断请求 ctx 的取消信号(保证请求结束后 goroutine 仍能运行),再补 `svc.ProcessImageAsync` span 聚合异步分支。 ## Implementation Notes - service 层 span 只做 start/end,不调用 `span.RecordError`:service 方法返回 `*errcode.Error` 且在多个 return 点返回,若记录错误需改成具名返回值,侵入过大;本次目标为调用链可读,span 嵌套与耗时已满足需求。 - 需新增 `global` import 的文件:order.go、goods.go、outfit.go、horoscope.go、collect.go、closet.go、like.go、notification.go;airecommend.go、clothes.go、user.go、quota.go 已导入 `global`。 - clothes.go 需新增 `go.opentelemetry.io/otel/trace` import(go.mod 已包含该依赖)。 - 保持与现有 DAO 埋点风格一致:span 名用 `dao.Xxx`/`upload.Xxx` 相同的大写方法名风格,不引入新的埋点封装或 helper,避免与现有约定不一致。 ## Directory Structure ``` internal/service/ ├── clothes.go # [MODIFY] 6 个入口方法补 span;processImageAsync 签名改造、trace 传播修复、新增 svc.ProcessImageAsync span、更新调用点、新增 otel/trace import ├── outfit.go # [MODIFY] Create/GetByUUID/UpdateByUUID/List/Delete/Public 补 span;新增 global import ├── closet.go # [MODIFY] Create/Detail/List/Cancel 补 span;新增 global import ├── collect.go # [MODIFY] Collect/List 补 span;新增 global import ├── like.go # [MODIFY] LikeCreate/GetByTargetID 补 span;新增 global import ├── notification.go # [MODIFY] List/GetByUUID/Read/Unread 补 span;新增 global import ├── quota.go # [MODIFY] InfoGetBySelf/FlowList 补 span(global 已导入) ├── user.go # [MODIFY] WxLogin/InfoGetBySelf/ProfileUpdate/RefreshToken 补 span(global 已导入) ├── goods.go # [MODIFY] GetByCode/List 补 span;新增 global import ├── horoscope.go # [MODIFY] GetToday 补 span;新增 global import ├── order.go # [MODIFY] Create/PayNotify/Cancel/List/RefundApply/RefundNotify 补 span;新增 global import └── airecommend.go # [MODIFY] Create/Save 补 span(global 已导入) ```