Golang六边形架构中无需传Context获取RequestId日志方案咨询
在Golang六边形架构中全局获取RequestId的可行方案
核心思路:用Goroutine本地存储(GLS)实现请求级全局访问
Golang没有官方的线程本地存储,但可以基于goroutine ID实现请求范围的全局存储,让仓储、服务等底层组件不用显式传递Context就能拿到RequestId,完美适配六边形架构的依赖倒置原则。
1. 实现一个轻量的RequestId全局存储工具
自己写个小工具,用goroutine ID作为key来存储RequestId,保证每个请求的goroutine能拿到自己的专属ID:
package requestid import ( "sync" "runtime" ) var ( mu sync.RWMutex requestCache = make(map[uint64]string) ) // 解析当前goroutine的ID func getGoroutineID() uint64 { buf := make([]byte, 64) buf = buf[:runtime.Stack(buf, false)] var gid uint64 for _, c := range buf { if c >= '0' && c <= '9' { gid = gid*10 + uint64(c-'0') } else { break } } return gid } // Set 把RequestId存入当前goroutine的专属存储 func Set(reqID string) { mu.Lock() defer mu.Unlock() requestCache[getGoroutineID()] = reqID } // Get 从当前goroutine的存储中取出RequestId func Get() string { mu.RLock() defer mu.RUnlock() return requestCache[getGoroutineID()] } // Clear 请求结束后清理当前goroutine的存储,防止内存泄漏 func Clear() { mu.Lock() defer mu.Unlock() delete(requestCache, getGoroutineID()) }
2. 在GIN中间件中绑定RequestId
在请求入口的中间件里生成RequestId,同时存入GIN Context和全局存储,请求结束后自动清理:
package middleware import ( "github.com/gin-gonic/gin" "github.com/your-project/requestid" "github.com/google/uuid" ) func RequestIDMiddleware() gin.HandlerFunc { return func(c *gin.Context) { reqID := uuid.NewString() // 同时存入GIN Context(方便接口层使用)和全局存储 c.Set("requestId", reqID) requestid.Set(reqID) // 请求完成后清理全局存储,避免内存溢出 defer requestid.Clear() c.Next() } }
3. 封装日志工具自动携带RequestId
写个全局日志工具,每次打日志时自动从存储中取RequestId,不用业务层关心:
package logger import ( "fmt" "github.com/your-project/requestid" "log" ) func Info(msg string, args ...interface{}) { reqID := requestid.Get() if reqID != "" { msg = fmt.Sprintf("[RequestID: %s] %s", reqID, msg) } log.Printf(msg, args...) } // 同理可以封装Error、Warn等日志级别 func Error(msg string, args ...interface{}) { reqID := requestid.Get() if reqID != "" { msg = fmt.Sprintf("[RequestID: %s] ERROR: %s", reqID, msg) } log.Printf(msg, args...) }
4. 仓储层直接用日志工具就行
现在仓储适配器不用传Context,直接调用日志工具,日志里自动带上RequestId:
package repository import "github.com/your-project/logger" type UserRepo struct{} func (r *UserRepo) GetUser(userID int) (interface{}, error) { logger.Info("开始查询用户,ID: %d", userID) // ... 具体数据库操作 return nil, nil }
关键注意点
- 内存泄漏:一定要在请求结束时调用
Clear(),不然goroutine退出后对应的RequestId会留在map里,长期运行会占内存。 - 异步goroutine处理:如果请求里开了新的goroutine,要手动把RequestId传进去并设置,因为新goroutine有独立的ID,继承不了父goroutine的存储:
currentReqID := requestid.Get() go func() { requestid.Set(currentReqID) defer requestid.Clear() logger.Info("异步任务执行中") // ... 异步逻辑 }() - 架构合规性:这个方案完全符合六边形架构——全局存储和日志工具属于基础设施层,仓储适配器依赖基础设施层,核心业务逻辑完全不需要感知RequestId的存在,不会破坏依赖倒置原则。
内容的提问来源于stack exchange,提问作者Raju Yadav
相关产品推荐
相关产品推荐

