在Golang中,当panic发生时,确实很难直接获取到当前请求的trace ID。这是因为panic处理机制与常规的请求上下文是分离的。不过,我们可以通过一些技术手段来解决这个问题。
一种常见的方法是在每个请求的上下文中设置trace ID,并在panic发生时通过recover机制来获取这个trace ID。下面是一个示例实现:
package main
import (
"context"
"fmt"
"net/http"
"runtime/debug"
)
type traceIDKey struct{}
func WithTraceID(ctx context.Context, traceID string) context.Context {
return context.WithValue(ctx, traceIDKey{}, traceID)
}
func GetTraceID(ctx context.Context) string {
if id, ok := ctx.Value(traceIDKey{}).(string); ok {
return id
}
return ""
}
func PanicRecoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
traceID := GetTraceID(r.Context())
stack := debug.Stack()
fmt.Printf("Panic occurred!\n")
fmt.Printf("Trace ID: %s\n", traceID)
fmt.Printf("Error: %v\n", err)
fmt.Printf("Stack trace:\n%s\n", stack)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func TraceIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = generateTraceID() // 实现自己的trace ID生成逻辑
}
ctx := WithTraceID(r.Context(), traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func generateTraceID() string {
return "trace-123456" // 简化示例,实际应该使用UUID等
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("something went wrong")
})
handler := TraceIDMiddleware(PanicRecoverMiddleware(mux))
http.ListenAndServe(":8080", handler)
}
在这个示例中,我们创建了两个中间件:TraceIDMiddleware用于为每个请求设置trace ID,PanicRecoverMiddleware用于捕获panic并记录trace ID。当panic发生时,我们可以在recover函数中通过GetTraceID(r.Context())获取到当前请求的trace ID。
对于更复杂的应用,你可能需要集成更完整的追踪系统,比如OpenTelemetry。下面是一个使用OpenTelemetry的示例:
package main
import (
"context"
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
var tracer = otel.Tracer("myapp")
func PanicRecoverWithOTel(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
span := trace.SpanFromContext(r.Context())
if span.IsRecording() {
span.RecordError(err.(error))
span.SetAttributes(
attribute.String("panic.error", fmt.Sprintf("%v", err)),
)
}
span.End()
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
ctx, span := tracer.Start(r.Context(), "http.request")
defer span.End()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
使用OpenTelemetry可以让你获得更完整的分布式追踪能力,包括自动的trace ID生成和跨服务的追踪。