Files
pig-farm-controller/internal/infra/logs/context.go
2025-11-05 23:52:48 +08:00

91 lines
3.3 KiB
Go
Raw 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.

package logs
import (
"context"
"fmt"
"strings"
)
// 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
}
// 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 ""
}