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

View File

@ -20,21 +20,10 @@ const (
// NewHandler 创建一个更偏向于人类可读的处理程序,该处理程序也是默认的处理程序
func NewHandler(w io.Writer, opts *Options) slog.Handler {
if opts == nil {
opts = NewOptions().apply()
} else {
opts = opts.apply()
opts = NewOptions()
}
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{
opts: opts,
opts: opts.apply(),
w: w,
}
}
@ -51,16 +40,22 @@ type handler struct {
func (h *handler) clone() *handler {
return &handler{
groupPrefix: h.groupPrefix,
opts: NewOptions().With(h.opts).apply(),
groups: h.groups,
w: h.w,
}
}
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 {
lv := h.opts.Level.Load().(slog.Leveler).Level()
if r.Level < lv {
return nil
}
buf := newBuffer(h)
defer buf.Free()
@ -73,7 +68,9 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
buf.WriteBytes(' ')
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()
if f.File != "" {
src := &slog.Source{
@ -109,6 +106,9 @@ func (h *handler) Handle(_ context.Context, r slog.Record) error {
defer h.mu.Unlock()
_, err := h.w.Write(*buf.bytes)
if lv == DPanicLevel && h.opts.DevMode {
panic(r.Message)
}
return err
}

View File

@ -6,15 +6,21 @@ import (
)
// NewLogger 创建一个新的日志记录器
func NewLogger(options ...*Options) *Logger {
opts := NewOptions().With(options...)
func NewLogger(handlers ...slog.Handler) *Logger {
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{
Logger: slog.New(NewHandler(os.Stdout, opts)),
opts: opts,
Logger: slog.New(h),
}
}
type Logger struct {
*slog.Logger
opts *Options
}

View File

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