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 "" }