go项目脚手架
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.
sorbet/pkg/logs/logger.go

513 lines
11 KiB

package logs
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"log/slog"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
)
var (
// 使用东八区时间
// https://cloud.tencent.com/developer/article/1805859
cstZone = time.FixedZone("CST", 8*3600)
childLoggerKey = "sorbet/log:ChildLogger"
)
const (
Ldate = 1 << iota
Ltime
Lmicroseconds
Llongfile
Lshortfile
Lfields
Lcolor
LstdFlags = Ltime | Lmicroseconds | Lfields | Lcolor
)
type Logger interface {
SetFlags(flags int)
Flags() int
SetTimezone(loc *time.Location)
Timezone() *time.Location
SetLevel(level Level)
Level() Level
SetPersistWriter(w io.Writer)
SetWriter(w io.Writer)
With(args ...Attr) Logger
WithGroup(name string) Logger
Enabled(level Level) bool
Log(level Level, msg string, args ...any)
ForkLevel(level Level, msg string, args ...any) ChildLogger
Trace(msg string, args ...any)
ForkTrace(msg string, args ...any) ChildLogger
Debug(msg string, args ...any)
ForkDebug(msg string, args ...any) ChildLogger
Info(msg string, args ...any)
ForkInfo(msg string, args ...any) ChildLogger
Warn(msg string, args ...any)
ForkWarn(msg string, args ...any) ChildLogger
Error(msg string, args ...any)
ForkError(msg string, args ...any) ChildLogger
Fatal(msg string, args ...any)
ForkFatal(msg string, args ...any) ChildLogger
}
type ChildLogger interface {
Print(msg string, args ...any)
Finish()
}
type Options struct {
Flags int
Level Level
Timezone *time.Location
PersistWriter io.Writer
Writer io.Writer
}
type logger struct {
parent *logger
isChild int32
indent int32
level int32
flags int32
timezone unsafe.Pointer
outMu *sync.Mutex
isPersistDiscard int32
isDiscard int32
persistWriter io.Writer
writer io.Writer
handler slog.Handler
l *log.Logger
}
func New(opts *Options) Logger {
if opts.Flags == 0 {
opts.Flags = LstdFlags
}
if opts.Timezone == nil {
opts.Timezone = cstZone
}
if opts.PersistWriter == nil {
opts.PersistWriter = io.Discard
}
if opts.Writer == nil {
opts.Writer = os.Stderr
}
var l *logger
l = &logger{
outMu: &sync.Mutex{},
persistWriter: opts.PersistWriter,
writer: opts.Writer,
l: log.New(opts.Writer, "", 0),
}
l.SetLevel(opts.Level)
l.SetFlags(opts.Flags)
l.SetTimezone(opts.Timezone)
l.SetPersistWriter(opts.PersistWriter)
l.SetWriter(opts.Writer)
l.handler = slog.NewJSONHandler(opts.PersistWriter, &slog.HandlerOptions{
AddSource: true,
Level: opts.Level,
ReplaceAttr: l.onAttr,
})
return l
}
func (l *logger) SetFlags(flags int) {
atomic.StoreInt32(&l.flags, int32(flags))
}
func (l *logger) Flags() int {
return int(atomic.LoadInt32(&l.flags))
}
func (l *logger) SetTimezone(loc *time.Location) {
// FIXME(hupeh): 如何原子化储存结构体实例
atomic.StorePointer(&l.timezone, unsafe.Pointer(loc))
}
func (l *logger) Timezone() *time.Location {
return (*time.Location)(atomic.LoadPointer(&l.timezone))
}
func (l *logger) SetLevel(level Level) {
atomic.StoreInt32(&l.level, int32(level))
}
func (l *logger) Level() Level {
return Level(int(atomic.LoadInt32(&l.level)))
}
func (l *logger) SetPersistWriter(w io.Writer) {
l.outMu.Lock()
defer l.outMu.Unlock()
l.persistWriter = w
atomic.StoreInt32(&l.isPersistDiscard, discard(w))
}
func (l *logger) SetWriter(w io.Writer) {
l.outMu.Lock()
defer l.outMu.Unlock()
l.writer = w
atomic.StoreInt32(&l.isDiscard, discard(w))
}
func discard(w io.Writer) int32 {
if w == io.Discard {
return 1
}
return 0
}
func (l *logger) onAttr(_ []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.LevelKey:
level := a.Value.Any().(slog.Level)
levelLabel := parseSlogLevel(level).String()
a.Value = slog.StringValue(levelLabel)
case slog.TimeKey:
t := a.Value.Any().(time.Time)
a.Value = slog.TimeValue(t.In(l.Timezone()))
case slog.SourceKey:
s := a.Value.Any().(*slog.Source)
var as []Attr
if s.Function != "" {
as = append(as, String("func", s.Function))
}
if s.File != "" {
as = append(as, String("file", s.File))
}
if s.Line != 0 {
as = append(as, Int("line", s.Line))
}
a.Value = slog.GroupValue(as...)
}
return a
}
func (l *logger) Handle(ctx context.Context, r slog.Record) error {
if atomic.LoadInt32(&l.isDiscard) == 0 {
child, ok := ctx.Value(childLoggerKey).(*childLogger)
indent, err := l.println(child, r)
if err != nil {
return err
}
if ok && indent > 0 {
atomic.StoreInt32(&child.indent, indent)
}
}
if atomic.LoadInt32(&l.isPersistDiscard) == 0 {
return l.handler.Handle(ctx, r)
}
return nil
}
func (l *logger) println(child *childLogger, r slog.Record) (int32, error) {
var output string
var sep string
var indent int32
write := func(s string) {
if sep == "" {
sep = " "
} else {
output += sep
}
output += s
}
flags := l.Flags()
colorful := flags&Lcolor != 0
msg := r.Message
level := parseSlogLevel(r.Level)
levelStr := level.String()
withChild := child != nil
if withChild {
indent = atomic.LoadInt32(&child.indent)
withChild = indent > 0
}
if withChild {
write(strings.Repeat(" ", int(indent)))
indent = 0
} else {
if flags&(Ldate|Ltime|Lmicroseconds) != 0 {
t := r.Time.In(l.Timezone())
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"))
}
}
}
// 保存缩进
indent += int32(len(output) + len(levelStr) + 3)
if colorful {
switch level {
case LevelDebug:
levelStr = FgCyan.Wrap(levelStr)
msg = FgCyan.Wrap(msg)
case LevelInfo:
levelStr = FgBlue.Wrap(levelStr) + " "
msg = FgBlue.Wrap(msg)
indent += 1
case LevelWarn:
levelStr = FgYellow.Wrap(levelStr) + " "
msg = FgYellow.Wrap(msg)
indent += 1
case LevelError:
levelStr = FgRed.Wrap(levelStr)
msg = FgRed.Wrap(msg)
case LevelFatal:
levelStr = FgMagenta.Wrap(levelStr)
msg = FgMagenta.Wrap(msg)
}
levelStr = FgHiBlack.Wrap("[") + levelStr + FgHiBlack.Wrap("]")
} else {
levelStr = "[" + r.Level.String() + "]"
}
write(levelStr)
}
write(msg)
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 = fgGreenItalic.Wrap(fileStr)
}
write(fileStr)
}
if numAttrs := r.NumAttrs(); flags&Lfields != 0 && numAttrs > 0 {
fields := make(map[string]any, numAttrs)
r.Attrs(func(a Attr) bool {
fields[a.Key] = a.Value.Any()
return true
})
b, err := json.Marshal(fields)
if err != nil {
return 0, err
}
fieldsStr := string(bytes.TrimSpace(b))
if fieldsStr != "" {
if colorful {
fieldsStr = FgHiBlack.Wrap(fieldsStr)
}
write(fieldsStr)
}
}
l.l.Println(output)
return indent, nil
}
func (l *logger) clone() *logger {
c := *l
return &c
}
func (l *logger) With(args ...Attr) Logger {
if len(args) == 0 {
return l
}
c := l.clone()
c.handler = c.handler.WithAttrs(args)
return c
}
func (l *logger) WithGroup(name string) Logger {
if name == "" {
return l
}
c := l.clone()
c.handler = c.handler.WithGroup(name)
return c
}
func (l *logger) Enabled(level Level) bool {
return l.handler.Enabled(nil, level.slog().Level())
}
// Log logs at level.
func (l *logger) Log(level Level, msg string, args ...any) {
l.log(nil, level, msg, args...)
}
func (l *logger) ForkLevel(level Level, msg string, args ...any) ChildLogger {
c := &childLogger{
parent: l,
level: level,
indent: 0,
records: make([]slog.Record, 0),
closed: make(chan struct{}),
}
c.Print(msg, args...)
return c
}
// Trace logs at LevelTrace.
func (l *logger) Trace(msg string, args ...any) {
l.log(nil, LevelTrace, msg, args...)
}
func (l *logger) ForkTrace(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelTrace, msg, args...)
}
// Debug logs at LevelDebug.
func (l *logger) Debug(msg string, args ...any) {
l.log(nil, LevelDebug, msg, args...)
}
func (l *logger) ForkDebug(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelDebug, msg, args...)
}
// Info logs at LevelInfo.
func (l *logger) Info(msg string, args ...any) {
l.log(nil, LevelInfo, msg, args...)
}
func (l *logger) ForkInfo(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelInfo, msg, args...)
}
// Warn logs at LevelWarn.
func (l *logger) Warn(msg string, args ...any) {
l.log(nil, LevelWarn, msg, args...)
}
func (l *logger) ForkWarn(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelWarn, msg, args...)
}
// Error logs at LevelError.
func (l *logger) Error(msg string, args ...any) {
l.log(nil, LevelError, msg, args...)
}
func (l *logger) ForkError(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelError, msg, args...)
}
// Fatal logs at LevelFatal.
func (l *logger) Fatal(msg string, args ...any) {
l.log(nil, LevelFatal, msg, args...)
}
func (l *logger) ForkFatal(msg string, args ...any) ChildLogger {
return l.ForkLevel(LevelFatal, msg, args...)
}
func (l *logger) log(ctx context.Context, level Level, msg string, args ...any) {
if !l.Enabled(level) {
return
}
if ctx == nil {
ctx = context.Background()
}
_ = l.Handle(ctx, newRecord(level, msg, args))
}
func newRecord(level Level, msg string, args []any) slog.Record {
//var pc uintptr
//if !internal.IgnorePC {
// var pcs [1]uintptr
// // skip [runtime.Callers, this function, this function's caller]
// runtime.Callers(3, pcs[:])
// pc = pcs[0]
//}
//r := slog.NewRecord(time.Now(), level.slog().Level(), msg, pc)
r := slog.NewRecord(time.Now(), level.slog().Level(), msg, 0)
if len(args) > 0 {
var sprintfArgs []any
for _, arg := range args {
switch v := arg.(type) {
case Attr:
r.AddAttrs(v)
default:
sprintfArgs = append(sprintfArgs, arg)
}
}
if len(sprintfArgs) > 0 {
r.Message = fmt.Sprintf(msg, sprintfArgs...)
}
}
return r
}
type childLogger struct {
parent *logger
level Level
indent int32
begin slog.Record
finish slog.Record
records []slog.Record
closed chan struct{}
}
func (c *childLogger) Print(msg string, args ...any) {
select {
case <-c.closed:
default:
c.records = append(
c.records,
newRecord(c.level, msg, args),
)
}
}
func (c *childLogger) Finish() {
select {
case <-c.closed:
return
default:
close(c.closed)
}
ctx := context.Background()
ctx = context.WithValue(ctx, childLoggerKey, c)
for _, record := range c.records {
_ = c.parent.Handle(ctx, record)
}
}