2025-11-05 14:58:13 +08:00
|
|
|
|
package logs
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
2025-11-05 23:52:48 +08:00
|
|
|
|
"strings"
|
2025-11-05 14:58:13 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// contextKey 是用于在 context.Context 中存储值的私有类型,避免键冲突。
|
|
|
|
|
|
type contextKey string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// compNameKey 用于存储组件名称。
|
|
|
|
|
|
compNameKey contextKey = "compName"
|
|
|
|
|
|
// chainKey 用于存储调用链。
|
|
|
|
|
|
chainKey contextKey = "chain"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// AddCompName 将一个组件名(对象名)存入 Context,并返回一个包含该信息的新 Context。
|
|
|
|
|
|
// 这通常在依赖注入时完成,用于创建组件的“身份名牌” selfCtx。
|
|
|
|
|
|
func AddCompName(ctx context.Context, compName string) context.Context {
|
|
|
|
|
|
return context.WithValue(ctx, compNameKey, compName)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddFuncName 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息,
|
|
|
|
|
|
// 生成并返回一个包含更新后调用链和当前组件名的 新 Context。
|
|
|
|
|
|
// 此函数用于只需要传递调用链而不需要立即打印日志的场景。
|
|
|
|
|
|
func AddFuncName(upstreamCtx context.Context, selfCtx context.Context, funcName string) context.Context {
|
|
|
|
|
|
// 1. 获取上游调用链
|
|
|
|
|
|
var oldChain []string
|
|
|
|
|
|
if val := upstreamCtx.Value(chainKey); val != nil {
|
|
|
|
|
|
if chain, ok := val.([]string); ok {
|
|
|
|
|
|
oldChain = chain
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取当前组件名
|
|
|
|
|
|
compName, ok := selfCtx.Value(compNameKey).(string)
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
// 如果 selfCtx 中没有 compName,则无法构建节点,直接返回 upstreamCtx
|
|
|
|
|
|
// 这种情况通常不应该发生,因为 selfCtx 应该在依赖注入时通过 AddCompName 初始化
|
|
|
|
|
|
return upstreamCtx
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建新节点
|
|
|
|
|
|
newNode := fmt.Sprintf("%s.%s", compName, funcName)
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 生成新调用链
|
|
|
|
|
|
// 创建一个新的切片,并将旧链和新节点复制进去
|
|
|
|
|
|
newChain := make([]string, len(oldChain)+1)
|
|
|
|
|
|
copy(newChain, oldChain)
|
|
|
|
|
|
newChain[len(oldChain)] = newNode
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 创建新的 Context
|
|
|
|
|
|
// 使用 context.WithValue,以 chainKey 为键,将 newChain 存入一个新的 Context,我们称之为 tmpCtx。
|
|
|
|
|
|
tmpCtx := context.WithValue(upstreamCtx, chainKey, newChain)
|
|
|
|
|
|
|
|
|
|
|
|
// 接着,基于 tmpCtx,再次调用 context.WithValue,以 compNameKey 为键,
|
|
|
|
|
|
// 将从 selfCtx 中获取的 compName 存入,得到最终的 newCtx。
|
|
|
|
|
|
// 这确保了传向下游的 Context 正确地标识了当前组件。
|
|
|
|
|
|
newCtx := context.WithValue(tmpCtx, compNameKey, compName)
|
|
|
|
|
|
|
|
|
|
|
|
return newCtx
|
|
|
|
|
|
}
|
2025-11-05 23:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
// DetachContext 创建一个“分离”的 Context。
|
|
|
|
|
|
// 新的 Context 会继承原始 Context 中的所有值(特别是用于日志追踪的 chainKey 和 compNameKey),
|
|
|
|
|
|
// 但它会使用 context.Background() 作为其父级,从而“丢弃”原始 Context 的取消信号。
|
|
|
|
|
|
// 这对于需要在请求结束后继续执行的异步任务(如记录审计日志)至关重要,可以防止出现 "context canceled" 错误。
|
|
|
|
|
|
func DetachContext(ctx context.Context) context.Context {
|
|
|
|
|
|
detachedCtx := context.Background()
|
|
|
|
|
|
|
|
|
|
|
|
// 复制我们关心的、用于日志追踪的所有值
|
|
|
|
|
|
if val := ctx.Value(chainKey); val != nil {
|
|
|
|
|
|
detachedCtx = context.WithValue(detachedCtx, chainKey, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
if val := ctx.Value(compNameKey); val != nil {
|
|
|
|
|
|
detachedCtx = context.WithValue(detachedCtx, compNameKey, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return detachedCtx
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取context中的调用链字符串
|
|
|
|
|
|
func GetTraceStr(ctx context.Context) string {
|
|
|
|
|
|
if trace, ok := ctx.Value(chainKey).([]string); ok {
|
|
|
|
|
|
return strings.Join(trace, "->")
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|