102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
|
|
package logging
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"log/slog"
|
|||
|
|
"os"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// SlogLogger 基于 Go 1.21+ slog 的结构化日志实现
|
|||
|
|
type SlogLogger struct {
|
|||
|
|
*slog.Logger
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSlogLogger 创建 slog-based 日志实例
|
|||
|
|
func NewSlogLogger(service string, minLevel LogLevel) *SlogLogger {
|
|||
|
|
opts := &slog.HandlerOptions{
|
|||
|
|
Level: levelToSlog(minLevel),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
handler := slog.NewJSONHandler(os.Stdout, opts)
|
|||
|
|
logger := slog.New(handler)
|
|||
|
|
logger = logger.With("service", service)
|
|||
|
|
|
|||
|
|
return &SlogLogger{Logger: logger}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// levelToSlog 转换日志级别
|
|||
|
|
func levelToSlog(l LogLevel) slog.Level {
|
|||
|
|
switch l {
|
|||
|
|
case LogLevelDebug:
|
|||
|
|
return slog.LevelDebug
|
|||
|
|
case LogLevelInfo:
|
|||
|
|
return slog.LevelInfo
|
|||
|
|
case LogLevelWarn:
|
|||
|
|
return slog.LevelWarn
|
|||
|
|
case LogLevelError:
|
|||
|
|
return slog.LevelError
|
|||
|
|
case LogLevelFatal:
|
|||
|
|
return slog.LevelError // slog 没有 fatal,用 error 代替
|
|||
|
|
default:
|
|||
|
|
return slog.LevelInfo
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WithTraceID 返回带 trace_id 的日志实例
|
|||
|
|
func (l *SlogLogger) WithTraceID(ctx context.Context) *SlogLogger {
|
|||
|
|
traceID := GetTraceIDFromContext(ctx)
|
|||
|
|
if traceID == "" {
|
|||
|
|
return l
|
|||
|
|
}
|
|||
|
|
return &SlogLogger{Logger: l.Logger.With("trace_id", traceID)}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WithRequestID 返回带 request_id 的日志实例
|
|||
|
|
func (l *SlogLogger) WithRequestID(requestID string) *SlogLogger {
|
|||
|
|
if requestID == "" {
|
|||
|
|
return l
|
|||
|
|
}
|
|||
|
|
return &SlogLogger{Logger: l.Logger.With("request_id", requestID)}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// WithTenantID 返回带 tenant_id 的日志实例
|
|||
|
|
func (l *SlogLogger) WithTenantID(tenantID int64) *SlogLogger {
|
|||
|
|
return &SlogLogger{Logger: l.Logger.With("tenant_id", tenantID)}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTraceIDFromContext 从 context 提取 trace_id
|
|||
|
|
func GetTraceIDFromContext(ctx context.Context) string {
|
|||
|
|
if ctx == nil {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
// 尝试从 W3C traceparent 提取
|
|||
|
|
if traceparent, ok := ctx.Value("traceparent").(string); ok && traceparent != "" {
|
|||
|
|
// 格式: 00-{trace-id}-{span-id}-{trace-flags}
|
|||
|
|
return extractTraceID(traceparent)
|
|||
|
|
}
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// extractTraceID 从 traceparent 提取 trace_id
|
|||
|
|
func extractTraceID(traceparent string) string {
|
|||
|
|
// 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
|
|||
|
|
if len(traceparent) < 55 {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return traceparent[3:35] // 0af7651916cd43dd8448eb211c80319c
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Fields 便捷方法:使用 map 创建带字段的日志
|
|||
|
|
func (l *SlogLogger) Fields(fields map[string]interface{}) *SlogLogger {
|
|||
|
|
args := make([]any, 0, len(fields)*2)
|
|||
|
|
for k, v := range fields {
|
|||
|
|
args = append(args, k, v)
|
|||
|
|
}
|
|||
|
|
return &SlogLogger{Logger: l.Logger.With(args...)}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DebugInfo 记录调试信息
|
|||
|
|
func (l *SlogLogger) DebugInfo(msg string) {
|
|||
|
|
l.Debug(msg)
|
|||
|
|
}
|