Skip to content

Commit

Permalink
feat: 优化 log 包,支持动态修改日志级别
Browse files Browse the repository at this point in the history
  • Loading branch information
kercylan98 committed Jan 3, 2024
1 parent 1d09007 commit 3e41068
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 49 deletions.
16 changes: 8 additions & 8 deletions utils/log/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"errors"
"log/slog"
"os"
"runtime"
"sync/atomic"
"time"
)

var logger atomic.Pointer[Logger]

func init() {
logger.Store(NewLogger())
logger.Store(NewLogger(NewHandler(os.Stdout, NewOptions())))
}

// Default 获取默认的日志记录器
Expand All @@ -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...)
Expand All @@ -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...)
}
Expand All @@ -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))
}
}
21 changes: 15 additions & 6 deletions utils/log/field_test.go
Original file line number Diff line number Diff line change
@@ -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")
Expand Down
30 changes: 15 additions & 15 deletions utils/log/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
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
opts = NewOptions()
}

return &handler{
opts: opts,
opts: opts.apply(),
w: w,
}
}
Expand All @@ -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()

Expand All @@ -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{
Expand Down Expand Up @@ -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
}

Expand Down
16 changes: 11 additions & 5 deletions utils/log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
50 changes: 35 additions & 15 deletions utils/log/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package log

import (
"log/slog"
"sync/atomic"
"time"
)

Expand All @@ -10,7 +11,7 @@ const (
DefaultTimePrefix = ""
DefaultTimePrefixDelimiter = "="
DefaultCaller = true
DefaultCallerSkip = 3
DefaultCallerSkip = 4
DefaultFieldPrefix = ""
DefaultLevel = DebugLevel
DefaultErrTrace = true
Expand Down Expand Up @@ -54,16 +55,18 @@ 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 // 时间前缀分隔符
Caller bool // 是否显示调用者信息
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 // 键值对分隔符
Expand All @@ -81,22 +84,25 @@ 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
opts.Caller = DefaultCaller
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
Expand Down Expand Up @@ -150,6 +156,11 @@ func (o *Options) WithTest() *Options {
return o
}

// GerRuntimeHandler 获取运行时处理器
func (o *Options) GerRuntimeHandler() *RuntimeHandler {
return o.r
}

// WithDevMode 设置是否为开发模式
// - 默认值为 DefaultDevMode
// - 开发模式下将影响部分功能,例如 DPanic
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}

0 comments on commit 3e41068

Please sign in to comment.