Files
Logging-System/slogadapter/slogadapter.go
T
weichenandCursor bed0dbf076
ci / vet-and-test (push) Successful in 4s
ci / docker-checkout-hostgateway (push) Successful in 2s
Add a structured logger and collector so runs can be traced across processes.
Sync and async sinks, redaction, and a queryable HTTP service keep concurrent logs tied to a trace id.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 13:51:16 +00:00

160 lines
3.5 KiB
Go

// Package slogadapter 在 logsys 与标准库 log/slog 之间双向转发。
package slogadapter
import (
"context"
"log/slog"
"logging-system/logsys"
)
// Handler 把 slog 记录写入 logsys。
type Handler struct {
logger *logsys.Logger
group string
attrs []slog.Attr
}
// NewHandler 包装一个 logsys 日志器。
func NewHandler(l *logsys.Logger) *Handler {
if l == nil {
l = logsys.Nop()
}
return &Handler{logger: l}
}
// Slog 返回写入 logsys 的 *slog.Logger。
func Slog(l *logsys.Logger) *slog.Logger { return slog.New(NewHandler(l)) }
// Enabled 按 logsys 的级别过滤。
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
return h.logger.Enabled(fromSlogLevel(level))
}
// Handle 转发一条记录。
func (h *Handler) Handle(ctx context.Context, rec slog.Record) error {
kv := make([]any, 0, (len(h.attrs)+rec.NumAttrs())*2)
for _, attr := range h.attrs {
kv = append(kv, h.name(attr.Key), attr.Value.Any())
}
rec.Attrs(func(attr slog.Attr) bool {
kv = append(kv, h.name(attr.Key), attr.Value.Any())
return true
})
l := h.logger.WithContext(ctx)
switch {
case rec.Level >= slog.LevelError+4:
l.Fatal(rec.Message, nil, kv...)
case rec.Level >= slog.LevelError:
l.Error(rec.Message, nil, kv...)
case rec.Level >= slog.LevelWarn:
l.Warn(rec.Message, kv...)
case rec.Level >= slog.LevelInfo:
l.Info(rec.Message, kv...)
case rec.Level >= slog.LevelDebug:
l.Debug(rec.Message, kv...)
default:
l.Trace(rec.Message, kv...)
}
return nil
}
// WithAttrs 追加属性。
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
n := *h
n.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...)
return &n
}
// WithGroup 追加分组前缀。
func (h *Handler) WithGroup(name string) slog.Handler {
n := *h
if name == "" {
return &n
}
if n.group != "" {
n.group += "."
}
n.group += name
return &n
}
func (h *Handler) name(key string) string {
if h.group == "" {
return key
}
return h.group + "." + key
}
// Sink 把 logsys 条目写进 slog.Handler。
type Sink struct {
Handler slog.Handler
}
// Write 转发一条日志。
func (s Sink) Write(e logsys.Entry) error {
if s.Handler == nil {
return nil
}
rec := slog.NewRecord(e.Time, toSlogLevel(e.Level), e.Message, 0)
if e.Service != "" {
rec.AddAttrs(slog.String("service", e.Service))
}
if e.Module != "" {
rec.AddAttrs(slog.String("module", e.Module))
}
if e.TraceID != "" {
rec.AddAttrs(slog.String("trace_id", e.TraceID))
}
if e.Code != "" {
rec.AddAttrs(slog.String("code", e.Code))
}
if e.Error != "" {
rec.AddAttrs(slog.String("error", e.Error))
}
for k, v := range e.Fields {
rec.AddAttrs(slog.Any(k, v))
}
return s.Handler.Handle(context.Background(), rec)
}
// Sync 无独立缓冲。
func (s Sink) Sync() error { return nil }
// Close 不关闭 slog.Handler。
func (s Sink) Close() error { return nil }
func fromSlogLevel(level slog.Level) logsys.Level {
switch {
case level >= slog.LevelError+4:
return logsys.LevelFatal
case level >= slog.LevelError:
return logsys.LevelError
case level >= slog.LevelWarn:
return logsys.LevelWarn
case level >= slog.LevelInfo:
return logsys.LevelInfo
case level >= slog.LevelDebug:
return logsys.LevelDebug
default:
return logsys.LevelTrace
}
}
func toSlogLevel(level string) slog.Level {
switch level {
case "trace":
return slog.LevelDebug - 4
case "debug":
return slog.LevelDebug
case "warn":
return slog.LevelWarn
case "error":
return slog.LevelError
case "fatal":
return slog.LevelError + 4
default:
return slog.LevelInfo
}
}