提取 Context 中的链路信息

📅 2026/6/30 2:03:45
提取 Context 中的链路信息
提供了InfoContext、WarnContext等方法可以从context.Context中提取数据。默认情况下这些方法不会自动提取 context 中的值需要通过自定义 Handler 来实现。自定义 ContextHandler以下示例实现了一个自定义 Handler用于从 context 中提取 TraceIDpackage main import ( context log/slog os ) type contextKey string const TraceIDKey contextKey trace_id // ContextHandler 包装一个 slog.Handler在处理日志时自动从 context 中提取 TraceID type ContextHandler struct { slog.Handler } func (h *ContextHandler) Handle(ctx context.Context, record slog.Record) error { if ctx ! nil { if traceID, ok : ctx.Value(TraceIDKey).(string); ok traceID ! { record.AddAttrs(slog.String(string(TraceIDKey), traceID)) } } return h.Handler.Handle(ctx, record) } func main() { baseHandler : slog.NewJSONHandler(os.Stdout, nil) handler : ContextHandler{Handler: baseHandler} jsonLogger : slog.New(handler) slog.SetDefault(jsonLogger) ctx : context.WithValue(context.Background(), TraceIDKey, abc123-def456) slog.InfoContext(ctx, hello world) slog.WarnContext(ctx, something happened, user, zhangsan) }运行输出$ go run main.go | python3 -m json.tool { time: 2026-02-15T13:56:43.08632376908:00, level: INFO, msg: hello world, trace_id: abc123-def456 } { time: 2026-02-15T13:56:43.08632376908:00, level: WARN, msg: something happened, user: zhangsan, trace_id: abc123-def456 }在 Gin 框架中使用 slog在 Gin 中使用 slog 的 context 能力通常的做法是编写一个中间件来注入 TraceID并配合自定义slog.Handler来提取它。package main import ( context log/slog net net/http net/http/httputil os runtime/debug strings time github.com/gin-gonic/gin github.com/google/uuid ) type contextKey string const TraceIDKey contextKey trace_id // ContextHandler 从 context 中提取 TraceID 并添加到日志中 type ContextHandler struct { slog.Handler } func (h *ContextHandler) Handle(ctx context.Context, record slog.Record) error { if ctx ! nil { if traceID, ok : ctx.Value(TraceIDKey).(string); ok traceID ! { record.AddAttrs(slog.String(string(TraceIDKey), traceID)) } } return h.Handler.Handle(ctx, record) } // SlogMiddleware 是一个 Gin 中间件用于注入 TraceID func SlogMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start : time.Now() // 优先从请求头获取 TraceID没有则生成新的 traceID : c.GetHeader(X-Trace-ID) if traceID { traceID uuid.New().String() } // 将 TraceID 注入到标准的 context.Context 中 // 注意Gin 的 c.Set 只在 Gin 内部生效slog 需要标准库的 Context ctx : context.WithValue(c.Request.Context(), TraceIDKey, traceID) c.Request c.Request.WithContext(ctx) // 将 TraceID 写入响应头方便客户端追踪 c.Header(X-Trace-ID, traceID) c.Next() // 请求结束后的汇总日志 slog.InfoContext(c.Request.Context(), Request completed, slog.String(method, c.Request.Method), slog.String(path, c.Request.URL.Path), slog.Int(status, c.Writer.Status()), slog.Int(body_size, c.Writer.Size()), slog.Duration(latency, time.Since(start)), ) } } // SlogRecovery 是一个自定义的恢复中间件 // 它会捕获 Panic记录堆栈信息并使用 slog.ErrorContext 输出 func SlogRecovery() gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err : recover(); err ! nil { // 检查是否是连接中断broken pipe var brokenPipe bool if ne, ok : err.(*net.OpError); ok { if se, ok : ne.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(se.Error()), broken pipe) || strings.Contains(strings.ToLower(se.Error()), connection reset by peer) { brokenPipe true } } } // 获取堆栈信息 stack : string(debug.Stack()) // 获取原始请求内容 httpRequest, _ : httputil.DumpRequest(c.Request, false) if brokenPipe { slog.ErrorContext(c.Request.Context(), 网络连接中断, slog.Any(error, err), slog.String(request, string(httpRequest)), ) c.Error(err.(error)) c.Abort() return } // 记录 Panic 详情 slog.ErrorContext(c.Request.Context(), Recovery from panic, slog.Any(error, err), slog.String(stack, stack), slog.String(request, string(httpRequest)), ) ctx : c.Request.Context() traceID, _ : ctx.Value(TraceIDKey).(string) // 返回 500 状态码 c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ code: http.StatusInternalServerError, msg: Internal Server Error, data: nil, timestamp: time.Now().Format(time.RFC3339), trace_id: traceID, }) } }() c.Next() } } func main() { // 初始化 slog baseHandler : slog.NewJSONHandler(os.Stdout, slog.HandlerOptions{ Level: slog.LevelDebug, }) handler : ContextHandler{Handler: baseHandler} jsonLogger : slog.New(handler) slog.SetDefault(jsonLogger) // 使用 gin.New() 而不是 gin.Default()避免内置日志干扰 r : gin.New() r.Use(SlogMiddleware()) r.Use(SlogRecovery()) r.GET(/ping, func(c *gin.Context) { slog.InfoContext(c.Request.Context(), Processing /ping request, slog.String(user, zhangsan), ) time.Sleep(time.Second * 2) c.JSON(200, gin.H{msg: pong}) }) r.GET(/panic, func(c *gin.Context) { slog.InfoContext(c.Request.Context(), About to panic) panic(something went wrong) }) r.Run(:8080) }运行后测试$ curl http://localhost:8080/ping {msg:pong} $ curl http://localhost:8080/panic {code:500,msg:Internal Server Error,data:null,timestamp:2026-02-15T14:30:0008:00,trace_id:xxx-xxx-xxx}日志输出文件写日志文件一定要注意控制日志文件大小建议配合系统的logrotate。如果服务运行在kubernetes建议只输出控制台日志由专门的日志收集平台去获取控制台日志。基本实现写到app.log中package main