From 3e4106861967f0b8f7a57f5365c135fdd323f63e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 3 Jan 2024 17:55:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20log=20=E5=8C=85?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/log/default.go | 16 ++++++------- utils/log/field_test.go | 21 ++++++++++++----- utils/log/handler.go | 30 ++++++++++++------------- utils/log/logger.go | 16 ++++++++----- utils/log/options.go | 50 ++++++++++++++++++++++++++++------------- 5 files changed, 84 insertions(+), 49 deletions(-) diff --git a/utils/log/default.go b/utils/log/default.go index e06b5d6..dcc0fef 100644 --- a/utils/log/default.go +++ b/utils/log/default.go @@ -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 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 // - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“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)) - } } diff --git a/utils/log/field_test.go b/utils/log/field_test.go index bab95ae..5125aec 100644 --- a/utils/log/field_test.go +++ b/utils/log/field_test.go @@ -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") diff --git a/utils/log/handler.go b/utils/log/handler.go index dbbb0cb..6c0b84e 100644 --- a/utils/log/handler.go +++ b/utils/log/handler.go @@ -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 } diff --git a/utils/log/logger.go b/utils/log/logger.go index 554df3a..abc700a 100644 --- a/utils/log/logger.go +++ b/utils/log/logger.go @@ -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 } diff --git a/utils/log/options.go b/utils/log/options.go index 112ec85..6a4b83c 100644 --- a/utils/log/options.go +++ b/utils/log/options.go @@ -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) +}