feat: 优化 log 包,支持动态修改日志级别
This commit is contained in:
parent
1d09007eb8
commit
3e41068619
|
@ -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 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
|
||||||
// - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“development panic”)。这对于捕获可恢复但不应该发生的错误很有用
|
// - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue