You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
3.4 KiB
164 lines
3.4 KiB
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"log/slog"
|
|
"runtime"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
type Handler struct {
|
|
ctx unsafe.Pointer // 结构体 logger
|
|
color Colorer
|
|
slog.Handler
|
|
attrs []Attr
|
|
l *log.Logger
|
|
}
|
|
|
|
func (h *Handler) WithAttrs(attrs []Attr) slog.Handler {
|
|
return &Handler{
|
|
ctx: h.ctx,
|
|
color: h.color,
|
|
Handler: h.Handler.WithAttrs(attrs),
|
|
attrs: append(h.attrs, attrs...),
|
|
l: h.l,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) WithGroup(name string) slog.Handler {
|
|
return &Handler{
|
|
ctx: h.ctx,
|
|
color: h.color,
|
|
Handler: h.Handler.WithGroup(name),
|
|
l: h.l,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
|
|
// 参考 atomic.Pointer#Load 实现
|
|
l := (*logger)(atomic.LoadPointer(&h.ctx))
|
|
if atomic.LoadInt32(&l.discard) != 1 {
|
|
if len(h.attrs) > 0 {
|
|
x := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
|
|
r.Attrs(func(attr slog.Attr) bool {
|
|
x.AddAttrs(attr)
|
|
return true
|
|
})
|
|
x.AddAttrs(h.attrs...)
|
|
if err := h.Print(l, x); err != nil {
|
|
return err
|
|
}
|
|
} else if err := h.Print(l, r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if atomic.LoadInt32(&l.unpersist) != 1 {
|
|
return h.Handler.Handle(ctx, r)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Handler) Print(l *logger, r slog.Record) error {
|
|
flags := l.Flags()
|
|
colorful := flags&Lcolor != 0
|
|
level := parseSlogLevel(r.Level)
|
|
levelStr := level.String()
|
|
var fields map[string]any
|
|
if numAttrs := r.NumAttrs(); numAttrs > 0 {
|
|
fields = make(map[string]any, numAttrs)
|
|
r.Attrs(func(a Attr) bool {
|
|
switch a.Key {
|
|
case rawTimeKey:
|
|
r.Time = a.Value.Any().(time.Time)
|
|
case rawLevelKey:
|
|
levelStr = a.Value.Any().(string)
|
|
default:
|
|
fields[a.Key] = a.Value.Any()
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
var output string
|
|
var sep string
|
|
write := func(s string) {
|
|
if sep == "" {
|
|
sep = " "
|
|
} else {
|
|
output += sep
|
|
}
|
|
output += s
|
|
}
|
|
if flags&(Ldate|Ltime|Lmicroseconds) != 0 {
|
|
t := r.Time.In(l.Timezone()) // todo 原子操作
|
|
if flags&Ldate != 0 {
|
|
write(t.Format("2006/01/02"))
|
|
}
|
|
if flags&(Ltime|Lmicroseconds) != 0 {
|
|
if flags&Lmicroseconds != 0 {
|
|
write(t.Format("15:04:05.000"))
|
|
} else {
|
|
write(t.Format("15:04:05"))
|
|
}
|
|
}
|
|
}
|
|
if colorful {
|
|
// TODO(hupeh): 重新设计不同颜色
|
|
colorize := identify
|
|
switch level {
|
|
case LevelDebug:
|
|
colorize = h.color.Cyan
|
|
case LevelInfo:
|
|
colorize = h.color.Blue
|
|
case LevelWarn:
|
|
colorize = h.color.Yellow
|
|
case LevelError:
|
|
colorize = h.color.Red
|
|
case LevelFatal, LevelPanic:
|
|
colorize = h.color.Magenta
|
|
}
|
|
write(h.color.Grey("[") + colorize(levelStr) + h.color.Grey("]"))
|
|
write(colorize(r.Message))
|
|
} else {
|
|
write("[" + levelStr + "]")
|
|
write(r.Message)
|
|
}
|
|
if flags&(Lshortfile|Llongfile) != 0 && r.PC > 0 {
|
|
var fileStr string
|
|
fs := runtime.CallersFrames([]uintptr{r.PC})
|
|
f, _ := fs.Next()
|
|
file := f.File
|
|
if flags&Lshortfile != 0 {
|
|
i := strings.LastIndexAny(file, "\\/")
|
|
if i > -1 {
|
|
file = file[i+1:]
|
|
}
|
|
}
|
|
fileStr = fmt.Sprintf("%s:%s:%d", f.Function, file, f.Line)
|
|
if colorful {
|
|
fileStr = h.color.Green(fileStr)
|
|
}
|
|
write(fileStr)
|
|
}
|
|
if flags&Lfields != 0 && len(fields) > 0 {
|
|
b, err := json.Marshal(fields)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldsStr := string(bytes.TrimSpace(b))
|
|
if fieldsStr != "" {
|
|
if colorful {
|
|
fieldsStr = h.color.White(fieldsStr)
|
|
}
|
|
write(fieldsStr)
|
|
}
|
|
}
|
|
h.l.Println(strings.TrimSpace(output))
|
|
return nil
|
|
}
|
|
|