如何在Zerolog Hook中提取事件字段并集成Azure App Insights
Zerolog与Azure App Insights集成:字段获取、实现优化及API建议
我已经编写了Zerolog与Azure App Insights集成的代码,但卡在了Hook函数中如何获取事件字段,无法将这些数据作为自定义属性发送到App Insights。另外想请教有没有更简洁的实现方式,以及是否有开发者认为Zerolog应该暴露获取字段的公共函数?
package zeroappinsights import ( "context" "encoding/json" "github.com/gin-gonic/gin" "net/http" "os" "time" "github.com/microsoft/ApplicationInsights-Go/appinsights" "github.com/microsoft/ApplicationInsights-Go/appinsights/contracts" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/org/package/constants" "github.com/org/package/models" ) var levelMap = map[zerolog.Level]contracts.SeverityLevel{ zerolog.ErrorLevel: contracts.Error, zerolog.InfoLevel: contracts.Information, zerolog.DebugLevel: contracts.Verbose, zerolog.FatalLevel: contracts.Critical, zerolog.WarnLevel: contracts.Warning, } var appInsightsProperties = []string{ contracts.OperationId, contracts.OperationParentId, contracts.UserId, contracts.SessionId, } type TracingHook struct { env constants.Environment telemetryClient appinsights.TelemetryClient file *os.File eventsMap map[string]string } func NewAITracing(serviceName, instrumentationKey string, env constants.Environment, eventsMap map[string]string) *TracingHook { client := appinsights.NewTelemetryClient(instrumentationKey) client.Context().Tags.Cloud().SetRole(serviceName) if env.IsLocal() { file, err := os.OpenFile(serviceName+".log", os.O_CREATE, 0644) if err != nil { return nil } return &TracingHook{ telemetryClient: client, file: file, env: constants.Local, } } return &TracingHook{ telemetryClient: client, file: nil, env: env, eventsMap: eventsMap, } } func (h *TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { ctx := e.GetCtx() if appinsightsLevel, ok := levelMap[level]; ok { trace := appinsights.NewTraceTelemetry(msg, appinsightsLevel) for _, property := range appInsightsProperties { trace.Tags[property] = ctx.Value(property).(string) } //I want to add more properties from the event to trace.Properties trace.Timestamp = time.Now().UTC() if h.env.IsLocal() { // Marshal the request into a JSON byte slice traceJSON, err := json.Marshal(trace) if err != nil { log.Printf("error marshaling request: %v", err) return } if _, err = h.file.Write(append(traceJSON, '\n')); err != nil { log.Printf("error occurred while writing to file: %v", err) return } } else { go h.telemetryClient.Track(trace) } } } func (h *TracingHook) LoggingMiddleware(logger zerolog.Logger) func(c *gin.Context) { return func(c *gin.Context) { startTime := time.Now().UTC() telemetry := appinsights.NewEventTelemetry(h.eventsMap[c.Request.URL.Path]) h.telemetryClient.Track(telemetry) operationID := telemetry.Tags[contracts.OperationId] operationParentID := telemetry.Tags[contracts.OperationParentId] userID := telemetry.Tags[contracts.UserId] sessionID := telemetry.Tags[contracts.SessionId] if value, exists := c.Get("userProfile"); exists { profile := value.(models.UserProfile) userID = profile.ID sessionID = c.Request.Header.Get(constants.Session) } values := []string{operationID, operationParentID, userID, sessionID} ctx := c.Request.Context() for i, key := range appInsightsProperties { ctx = context.WithValue(ctx, key, values[i]) } loggerContext := logger.WithContext(ctx) c.Set("loggerCtx", loggerContext) c.Next() duration := time.Since(startTime) request := appinsights.NewRequestTelemetry(c.Request.Method, c.Request.URL.Path, duration, http.StatusText(c.Writer.Status())) request.Timestamp = time.Now().UTC() tags := request.Tags tags[contracts.OperationId] = operationID tags[contracts.OperationParentId] = operationParentID tags[contracts.UserId] = userID tags[contracts.SessionId] = sessionID request.Tags = tags if h.env.IsLocal() { // Marshal the request into a JSON byte slice requestJSON, err := json.Marshal(request) if err != nil { log.Printf("error marshaling request: %v", err) return } if _, err = h.file.Write(append(requestJSON, '\n')); err != nil { log.Printf("error occurred while writing to file: %v", err) return } return } h.telemetryClient.Track(request) } }
一、Hook中获取Zerolog事件字段的方案
Zerolog的Event结构体未直接暴露字段访问API,但可以通过克隆事件并序列化到内存缓冲区的方式解析字段,不影响原日志输出:
// 导入bytes和fmt包 import ( "bytes" "fmt" ) func (h *TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { ctx := e.GetCtx() if appinsightsLevel, ok := levelMap[level]; ok { trace := appinsights.NewTraceTelemetry(msg, appinsightsLevel) for _, property := range appInsightsProperties { if val := ctx.Value(property); val != nil { trace.Tags[property] = val.(string) } } // 解析事件中的自定义字段 var buf bytes.Buffer // 克隆事件避免干扰原日志流程 clonedEvent := e.With() clonedEvent.Str("msg", msg).Send(&buf) var eventData map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &eventData); err == nil { // 过滤默认字段,添加自定义属性到App Insights for k, v := range eventData { if k != "msg" && k != "level" && k != "time" { trace.Properties[k] = fmt.Sprintf("%v", v) } } } trace.Timestamp = time.Now().UTC() // 原本地/云端上报逻辑不变 if h.env.IsLocal() { traceJSON, err := json.Marshal(trace) if err != nil { log.Printf("error marshaling request: %v", err) return } if _, err = h.file.Write(append(traceJSON, '\n')); err != nil { log.Printf("error occurred while writing to file: %v", err) return } } else { go h.telemetryClient.Track(trace) } } }
二、更简洁的实现优化
1. 简化上下文传递逻辑
把OperationId等标签直接存入Gin上下文,避免嵌套context.WithValue:
// 中间件中替换原上下文传递代码 c.Set(contracts.OperationId, operationID) c.Set(contracts.OperationParentId, operationParentID) c.Set(contracts.UserId, userID) c.Set(contracts.SessionId, sessionID)
Hook中直接从Gin上下文获取:
if ginCtx, ok := ctx.(*gin.Context); ok { for _, property := range appInsightsProperties { if val, exists := ginCtx.Get(property); exists { trace.Tags[property] = val.(string) } } }
2. 抽取公共序列化逻辑
把本地日志的序列化代码抽成公共方法,避免重复:
func (h *TracingHook) writeLocalLog(telemetry interface{}) { data, err := json.Marshal(telemetry) if err != nil { log.Printf("marshal telemetry failed: %v", err) return } if _, err = h.file.Write(append(data, '\n')); err != nil { log.Printf("write local log failed: %v", err) } }
在Run方法和中间件中直接调用:
if h.env.IsLocal() { h.writeLocalLog(trace) } else { go h.telemetryClient.Track(trace) }
3. 懒加载本地日志文件
将文件打开逻辑移到第一次写入时,避免初始化失败导致Hook创建失败:
func (h *TracingHook) getLogFile(serviceName string) *os.File { if h.file == nil && h.env.IsLocal() { file, err := os.OpenFile(serviceName+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err == nil { h.file = file } } return h.file }
三、关于Zerolog暴露字段API的讨论
社区中确实有不少开发者提出过类似需求,但Zerolog的设计核心是高性能(零分配、低开销),直接暴露字段映射可能会破坏其性能特性,因此官方目前未提供相关API。
如果需要更直接的字段访问,可尝试:
- 在创建日志时手动将字段存入上下文,Hook中直接从上下文读取
- 向Zerolog仓库提交Issue,说明你的业务场景和需求,推动官方评估添加API的可能性
内容的提问来源于stack exchange,提问作者Hariharan Sivakumar
相关产品推荐
相关产品推荐

