feat: 优化 log 包,支持动态修改日志级别

This commit is contained in:
kercylan98 2024-01-03 17:55:47 +08:00
parent 1d09007eb8
commit 3e41068619
5 changed files with 84 additions and 49 deletions

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"log/slog" "log/slog"
"os" "os"
"runtime"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@ -13,7 +12,7 @@ import (
var logger atomic.Pointer[Logger] var logger atomic.Pointer[Logger]
func init() { func init() {
logger.Store(NewLogger()) logger.Store(NewLogger(NewHandler(os.Stdout, NewOptions())))
} }
// Default 获取默认的日志记录器 // Default 获取默认的日志记录器
@ -26,6 +25,11 @@ func SetDefault(l *Logger) {
logger.Store(l) logger.Store(l)
} }
// SetDefaultBySlog 设置默认的日志记录器
func SetDefaultBySlog(l *slog.Logger) {
logger.Store(&Logger{Logger: l})
}
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 // Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Debug(msg string, args ...any) { func Debug(msg string, args ...any) {
handle(DebugLevel, msg, args...) handle(DebugLevel, msg, args...)
@ -48,6 +52,7 @@ func Error(msg string, args ...any) {
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 // DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用 // - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
// - 该 panic 仅在 NewHandler 中创建的处理器会生效
func DPanic(msg string, args ...any) { func DPanic(msg string, args ...any) {
handle(DPanicLevel, msg, args...) handle(DPanicLevel, msg, args...)
} }
@ -69,12 +74,7 @@ func Fatal(msg string, args ...any) {
// handle 在指定的级别记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 // handle 在指定的级别记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func handle(level slog.Level, msg string, args ...any) { func handle(level slog.Level, msg string, args ...any) {
d := Default() d := Default()
pcs := make([]uintptr, 1) r := slog.NewRecord(time.Now(), level, msg, 0)
runtime.CallersFrames(pcs[:runtime.Callers(d.opts.CallerSkip, pcs)])
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
r.Add(args...) r.Add(args...)
_ = d.Handler().Handle(context.Background(), r) _ = d.Handler().Handle(context.Background(), r)
if level == DPanicLevel && d.opts.DevMode {
panic(errors.New(msg))
}
} }

View File

@ -1,16 +1,25 @@
package log_test package log
import ( import (
"github.com/kercylan98/minotaur/utils/log" "log/slog"
"testing" "testing"
"time"
) )
func TestStack(t *testing.T) { func TestStack(t *testing.T) {
log.Debug("TestStack") var i int
log.Info("TestStack") for {
log.Warn("TestStack") time.Sleep(time.Second)
log.Error("TestStack") Debug("TestStack")
Info("TestStack")
Warn("TestStack")
Error("TestStack")
i++
if i == 3 {
Default().Logger.Handler().(*handler).opts.GerRuntimeHandler().ChangeLevel(slog.LevelInfo)
}
}
//log.Panic("TestStack") //log.Panic("TestStack")
//log.DPanic("TestStack") //log.DPanic("TestStack")
//log.Fatal("TestStack") //log.Fatal("TestStack")

View File

@ -20,21 +20,10 @@ const (
// NewHandler 创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序 // NewHandler 创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序
func NewHandler(w io.Writer, opts *Options) slog.Handler { func NewHandler(w io.Writer, opts *Options) slog.Handler {
if opts == nil { if opts == nil {
opts = NewOptions().apply() opts = NewOptions()
} else {
opts = opts.apply()
} }
if len(opts.Handlers) > 0 {
var handlers = make([]slog.Handler, len(opts.Handlers))
for i, s := range opts.Handlers {
handlers[i] = s
}
mh := NewMultiHandler(handlers...)
return mh
}
return &handler{ return &handler{
opts: opts, opts: opts.apply(),
w: w, w: w,
} }
} }
@ -51,16 +40,22 @@ type handler struct {
func (h *handler) clone() *handler { func (h *handler) clone() *handler {
return &handler{ return &handler{
groupPrefix: h.groupPrefix, groupPrefix: h.groupPrefix,
opts: NewOptions().With(h.opts).apply(),
groups: h.groups, groups: h.groups,
w: h.w, w: h.w,
} }
} }
func (h *handler) Enabled(_ context.Context, level slog.Level) bool { func (h *handler) Enabled(_ context.Context, level slog.Level) bool {
return level >= h.opts.Level.Level() return level >= h.opts.Level.Load().(slog.Leveler).Level()
} }
func (h *handler) Handle(_ context.Context, r slog.Record) error { func (h *handler) Handle(_ context.Context, r slog.Record) error {
lv := h.opts.Level.Load().(slog.Leveler).Level()
if r.Level < lv {
return nil
}
buf := newBuffer(h) buf := newBuffer(h)
defer buf.Free() defer buf.Free()
@ -73,7 +68,9 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
buf.WriteBytes(' ') buf.WriteBytes(' ')
if h.opts.Caller { if h.opts.Caller {
fs := runtime.CallersFrames([]uintptr{r.PC}) pcs := make([]uintptr, 1)
runtime.CallersFrames(pcs[:runtime.Callers(h.opts.CallerSkip, pcs)])
fs := runtime.CallersFrames(pcs)
f, _ := fs.Next() f, _ := fs.Next()
if f.File != "" { if f.File != "" {
src := &slog.Source{ src := &slog.Source{
@ -109,6 +106,9 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
defer h.mu.Unlock() defer h.mu.Unlock()
_, err := h.w.Write(*buf.bytes) _, err := h.w.Write(*buf.bytes)
if lv == DPanicLevel && h.opts.DevMode {
panic(r.Message)
}
return err return err
} }

View File

@ -6,15 +6,21 @@ import (
) )
// NewLogger 创建一个新的日志记录器 // NewLogger 创建一个新的日志记录器
func NewLogger(options ...*Options) *Logger { func NewLogger(handlers ...slog.Handler) *Logger {
opts := NewOptions().With(options...) var h slog.Handler
switch len(handlers) {
case 0:
h = NewHandler(os.Stdout, nil)
case 1:
h = handlers[0]
default:
h = NewMultiHandler(handlers...)
}
return &Logger{ return &Logger{
Logger: slog.New(NewHandler(os.Stdout, opts)), Logger: slog.New(h),
opts: opts,
} }
} }
type Logger struct { type Logger struct {
*slog.Logger *slog.Logger
opts *Options
} }

View File

@ -2,6 +2,7 @@ package log
import ( import (
"log/slog" "log/slog"
"sync/atomic"
"time" "time"
) )
@ -10,7 +11,7 @@ const (
DefaultTimePrefix = "" DefaultTimePrefix = ""
DefaultTimePrefixDelimiter = "=" DefaultTimePrefixDelimiter = "="
DefaultCaller = true DefaultCaller = true
DefaultCallerSkip = 3 DefaultCallerSkip = 4
DefaultFieldPrefix = "" DefaultFieldPrefix = ""
DefaultLevel = DebugLevel DefaultLevel = DebugLevel
DefaultErrTrace = true DefaultErrTrace = true
@ -54,8 +55,10 @@ type (
Option func(opts *Options) Option func(opts *Options)
// Options 日志选项 // Options 日志选项
Options struct { Options struct {
opts []Option r *RuntimeHandler // 运行时处理器
Handlers []slog.Handler // 处理程序 opts []Option // 可选项
applyed bool // 是否已应用
TimeLayout string // 时间格式化字符串 TimeLayout string // 时间格式化字符串
TimePrefix string // 时间前缀 TimePrefix string // 时间前缀
TimePrefixDelimiter string // 时间前缀分隔符 TimePrefixDelimiter string // 时间前缀分隔符
@ -63,7 +66,7 @@ type (
CallerSkip int // 跳过的调用层数 CallerSkip int // 跳过的调用层数
CallerFormat func(file string, line int) (repFile, refLine string) // 调用者信息格式化函数 CallerFormat func(file string, line int) (repFile, refLine string) // 调用者信息格式化函数
FieldPrefix string // 字段前缀 FieldPrefix string // 字段前缀
Level slog.Leveler // 日志级别 Level atomic.Value // 日志级别
ErrTrace bool // 是否显示错误堆栈 ErrTrace bool // 是否显示错误堆栈
ErrTraceBeauty bool // 是否美化错误堆栈 ErrTraceBeauty bool // 是否美化错误堆栈
KVDelimiter string // 键值对分隔符 KVDelimiter string // 键值对分隔符
@ -81,14 +84,17 @@ type (
MessageColor string // 消息颜色 MessageColor string // 消息颜色
DevMode bool // 是否为开发模式 DevMode bool // 是否为开发模式
} }
RuntimeHandler struct {
opts *Options
}
) )
// WithDev 设置可选项为开发模式 // WithDev 设置可选项为开发模式
// - 开发模式适用于本地开发环境,会以更友好的方式输出日志 // - 开发模式适用于本地开发环境,会以更友好的方式输出日志
func (o *Options) WithDev() *Options { func (o *Options) WithDev() *Options {
o.opts = append(o.opts, func(opts *Options) { o.opts = append(o.opts, func(opts *Options) {
opts.r = &RuntimeHandler{opts: opts}
opts.DevMode = DefaultDevMode opts.DevMode = DefaultDevMode
opts.Handlers = nil
opts.TimeLayout = DefaultTimeLayout opts.TimeLayout = DefaultTimeLayout
opts.TimePrefix = DefaultTimePrefix opts.TimePrefix = DefaultTimePrefix
opts.TimePrefixDelimiter = DefaultTimePrefixDelimiter opts.TimePrefixDelimiter = DefaultTimePrefixDelimiter
@ -96,7 +102,7 @@ func (o *Options) WithDev() *Options {
opts.CallerSkip = DefaultCallerSkip opts.CallerSkip = DefaultCallerSkip
opts.CallerFormat = CallerBasicFormat opts.CallerFormat = CallerBasicFormat
opts.FieldPrefix = DefaultFieldPrefix opts.FieldPrefix = DefaultFieldPrefix
opts.Level = DefaultLevel opts.Level.Store(DefaultLevel)
opts.ErrTrace = DefaultErrTrace opts.ErrTrace = DefaultErrTrace
opts.ErrTraceBeauty = DefaultErrTraceBeauty opts.ErrTraceBeauty = DefaultErrTraceBeauty
opts.KVDelimiter = DefaultKVDelimiter opts.KVDelimiter = DefaultKVDelimiter
@ -150,6 +156,11 @@ func (o *Options) WithTest() *Options {
return o return o
} }
// GerRuntimeHandler 获取运行时处理器
func (o *Options) GerRuntimeHandler() *RuntimeHandler {
return o.r
}
// WithDevMode 设置是否为开发模式 // WithDevMode 设置是否为开发模式
// - 默认值为 DefaultDevMode // - 默认值为 DefaultDevMode
// - 开发模式下将影响部分功能,例如 DPanic // - 开发模式下将影响部分功能,例如 DPanic
@ -160,14 +171,6 @@ func (o *Options) WithDevMode(enable bool) *Options {
return o return o
} }
// WithHandler 设置处理程序
func (o *Options) WithHandler(handlers ...slog.Handler) *Options {
o.append(func(opts *Options) {
opts.Handlers = handlers
})
return o
}
// WithMessageColor 设置消息颜色 // WithMessageColor 设置消息颜色
// - 默认消息颜色为 DefaultMessageColor // - 默认消息颜色为 DefaultMessageColor
func (o *Options) WithMessageColor(color string) *Options { func (o *Options) WithMessageColor(color string) *Options {
@ -318,7 +321,7 @@ func (o *Options) WithErrTrace(enable bool) *Options {
// - 默认日志级别为 DefaultLevel // - 默认日志级别为 DefaultLevel
func (o *Options) WithLevel(level slog.Leveler) *Options { func (o *Options) WithLevel(level slog.Leveler) *Options {
o.append(func(opts *Options) { o.append(func(opts *Options) {
opts.Level = level opts.Level.Store(level)
}) })
return o return o
} }
@ -387,6 +390,10 @@ func (o *Options) With(opts ...*Options) *Options {
// apply 应用日志选项 // apply 应用日志选项
func (o *Options) apply() *Options { func (o *Options) apply() *Options {
if o.applyed {
return o
}
o.applyed = true
for _, opt := range o.opts { for _, opt := range o.opts {
opt(o) opt(o)
} }
@ -395,6 +402,19 @@ func (o *Options) apply() *Options {
// append 添加日志选项 // append 添加日志选项
func (o *Options) append(opts ...Option) *Options { func (o *Options) append(opts ...Option) *Options {
if o.applyed {
return o
}
o.opts = append(o.opts, opts...) o.opts = append(o.opts, opts...)
return o return o
} }
// ChangeLevel 改变日志级别
func (h *RuntimeHandler) ChangeLevel(level slog.Leveler) {
h.opts.Level.Store(level)
}
// Level 获取日志级别
func (h *RuntimeHandler) Level() slog.Level {
return h.opts.Level.Load().(slog.Level)
}