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