other: 新版 server、logger 完善

This commit is contained in:
kercylan98 2024-04-02 19:22:39 +08:00
parent 49b8efd9b2
commit e4eee31ede
22 changed files with 1532 additions and 72 deletions

5
go.mod
View File

@ -28,9 +28,11 @@ require (
)
require (
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
@ -47,7 +49,8 @@ require (
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect

12
go.sum
View File

@ -5,6 +5,8 @@ github.com/RussellLuo/timingwheel v0.0.0-20220218152713-54845bda3108/go.mod h1:W
github.com/alphadose/haxmap v1.3.1 h1:KmZh75duO1tC8pt3LmUwoTYiZ9sh4K52FX8p7/yrlqU=
github.com/alphadose/haxmap v1.3.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8 h1:8LX2T6XzOOPvVMS8RH0sY4+QFmO5XyFUnrmwVbtD13k=
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@ -22,6 +24,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
@ -104,9 +108,14 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -231,6 +240,7 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
@ -259,6 +269,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -4,6 +4,7 @@ import (
"net"
)
// ConnWriter 用于兼容不同 Network 的连接数据写入器
type ConnWriter func(packet Packet) error
type Conn interface {

View File

@ -1,4 +0,0 @@
package server
type ConnContext interface {
}

View File

@ -1,6 +1,9 @@
package server
import (
"fmt"
"github.com/kercylan98/minotaur/utils/log"
"reflect"
"time"
"github.com/kercylan98/minotaur/utils/collection/listings"
@ -57,6 +60,12 @@ func (s *events) RegisterLaunchedEvent(handler LaunchedEventHandler, priority ..
}
func (s *events) onLaunched() {
s.Options.getManyOptions(func(opt *Options) {
opt.logger.Info("Minotaur Server", log.String("", "============================================================================"))
opt.logger.Info("Minotaur Server", log.String("", "RunningInfo"), log.String("network", reflect.TypeOf(s.network).String()), log.String("listen", fmt.Sprintf("%s://%s%s", s.network.Schema(), s.server.state.Ip, s.network.Address())))
opt.logger.Info("Minotaur Server", log.String("", "============================================================================"))
})
_ = s.server.reactor.SystemDispatch(NativeMessage(s.server, func(srv *server) {
s.launchedEventHandlers.RangeValue(func(index int, value LaunchedEventHandler) bool {
value(s.server, s.server.state.Ip, s.server.state.LaunchedAt)

View File

@ -4,10 +4,20 @@ import (
"context"
)
// Network 提供给 Server 使用的网络接口
type Network interface {
// OnSetup 装载函数,该函数将传入 Server 的上下文及一个可用于控制 Server 行为的控制器,在该函数中应该完成 Network 实现的初始化工作
OnSetup(ctx context.Context, controller Controller) error
// OnRun 运行函数,在该函数中通常是进行网络监听启动的阻塞行为
OnRun() error
// OnShutdown 停止函数,该函数在执行的时候应该完成对资源的停止及释放
OnShutdown() error
// Schema 获取协议标识
Schema() string
// Address 获取监听地址信息
Address() string
}

View File

@ -53,3 +53,11 @@ func (h *httpCore[H]) OnShutdown() error {
defer cancel()
return h.srv.Shutdown(ctx)
}
func (h *httpCore[H]) Schema() string {
return "http(s)"
}
func (h *httpCore[H]) Address() string {
return h.srv.Addr
}

View File

@ -45,3 +45,14 @@ func (w *websocketCore) OnShutdown() error {
}
return nil
}
func (w *websocketCore) Schema() string {
return "ws"
}
func (w *websocketCore) Address() string {
if w.pattern == "/" {
return w.addr
}
return fmt.Sprintf("%s:%s", w.addr, w.pattern)
}

View File

@ -1,6 +1,7 @@
package server
import (
"github.com/kercylan98/minotaur/utils/log/v2"
"sync"
"time"
)
@ -11,24 +12,27 @@ func NewOptions() *Options {
func DefaultOptions() *Options {
return &Options{
ServerMessageChannelSize: 1024 * 4,
ActorMessageChannelSize: 1024,
ServerMessageBufferInitialSize: 1024,
ActorMessageBufferInitialSize: 1024,
MessageErrorHandler: nil,
LifeCycleLimit: 0,
serverMessageChannelSize: 1024 * 4,
actorMessageChannelSize: 1024,
serverMessageBufferInitialSize: 1024,
actorMessageBufferInitialSize: 1024,
messageErrorHandler: nil,
lifeCycleLimit: 0,
logger: log.GetLogger(),
}
}
type Options struct {
server *server
rw sync.RWMutex
ServerMessageChannelSize int // 服务器 Actor 消息处理管道大小
ActorMessageChannelSize int // Actor 消息处理管道大小
ServerMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
ActorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
MessageErrorHandler func(srv Server, message Message, err error) // 消息错误处理器
LifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
serverMessageChannelSize int // 服务器 Actor 消息处理管道大小
actorMessageChannelSize int // Actor 消息处理管道大小
serverMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
actorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
messageErrorHandler func(srv Server, message Message, err error) // 消息错误处理器
lifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
logger *log.Logger // 日志记录器
debug bool // Debug 模式
}
func (opt *Options) init(srv *server) *Options {
@ -42,12 +46,13 @@ func (opt *Options) Apply(options ...*Options) {
for _, option := range options {
option.rw.RLock()
opt.ServerMessageChannelSize = option.ServerMessageChannelSize
opt.ActorMessageChannelSize = option.ActorMessageChannelSize
opt.ServerMessageBufferInitialSize = option.ServerMessageBufferInitialSize
opt.ActorMessageBufferInitialSize = option.ActorMessageBufferInitialSize
opt.MessageErrorHandler = option.MessageErrorHandler
opt.LifeCycleLimit = option.LifeCycleLimit
opt.serverMessageChannelSize = option.serverMessageChannelSize
opt.actorMessageChannelSize = option.actorMessageChannelSize
opt.serverMessageBufferInitialSize = option.serverMessageBufferInitialSize
opt.actorMessageBufferInitialSize = option.actorMessageBufferInitialSize
opt.messageErrorHandler = option.messageErrorHandler
opt.lifeCycleLimit = option.lifeCycleLimit
opt.logger = option.logger
option.rw.RUnlock()
}
@ -57,32 +62,62 @@ func (opt *Options) Apply(options ...*Options) {
}
func (opt *Options) active() {
opt.server.notify.lifeCycleTime <- opt.getLifeCycleLimit()
opt.server.notify.lifeCycleTime <- opt.GetLifeCycleLimit()
}
// WithDebug 设置 Debug 模式是否开启
// - 该函数支持运行时设置
func (opt *Options) WithDebug(debug bool) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.debug = true
})
}
// IsDebug 获取当前服务器是否是 Debug 模式
func (opt *Options) IsDebug() bool {
return getOptionsValue(opt, func(opt *Options) bool {
return opt.debug
})
}
// WithLogger 设置服务器的日志记录器
// - 该函数支持运行时设置
func (opt *Options) WithLogger(logger *log.Logger) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.logger = logger
})
}
// GetLogger 获取服务器的日志记录器
func (opt *Options) GetLogger() *log.Logger {
return getOptionsValue(opt, func(opt *Options) *log.Logger {
return opt.logger
})
}
// WithServerMessageChannelSize 设置服务器 Actor 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
func (opt *Options) WithServerMessageChannelSize(size int) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.ServerMessageChannelSize = size
opt.serverMessageChannelSize = size
})
}
func (opt *Options) getServerMessageChannelSize() int {
func (opt *Options) GetServerMessageChannelSize() int {
return getOptionsValue(opt, func(opt *Options) int {
return opt.ServerMessageChannelSize
return opt.serverMessageChannelSize
})
}
// WithActorMessageChannelSize 设置 Actor 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
func (opt *Options) WithActorMessageChannelSize(size int) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.ActorMessageChannelSize = size
opt.actorMessageChannelSize = size
})
}
func (opt *Options) getActorMessageChannelSize() int {
func (opt *Options) GetActorMessageChannelSize() int {
return getOptionsValue(opt, func(opt *Options) int {
return opt.ActorMessageChannelSize
return opt.actorMessageChannelSize
})
}
@ -90,13 +125,13 @@ func (opt *Options) getActorMessageChannelSize() int {
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
func (opt *Options) WithServerMessageBufferInitialSize(size int) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.ServerMessageBufferInitialSize = size
opt.serverMessageBufferInitialSize = size
})
}
func (opt *Options) getServerMessageBufferInitialSize() int {
func (opt *Options) GetServerMessageBufferInitialSize() int {
return getOptionsValue(opt, func(opt *Options) int {
return opt.ServerMessageBufferInitialSize
return opt.serverMessageBufferInitialSize
})
}
@ -104,13 +139,13 @@ func (opt *Options) getServerMessageBufferInitialSize() int {
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
func (opt *Options) WithActorMessageBufferInitialSize(size int) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.ActorMessageBufferInitialSize = size
opt.actorMessageBufferInitialSize = size
})
}
func (opt *Options) getActorMessageBufferInitialSize() int {
func (opt *Options) GetActorMessageBufferInitialSize() int {
return getOptionsValue(opt, func(opt *Options) int {
return opt.ActorMessageBufferInitialSize
return opt.actorMessageBufferInitialSize
})
}
@ -118,13 +153,13 @@ func (opt *Options) getActorMessageBufferInitialSize() int {
// - 如果在运行时设置,后续消息错误将会使用新的 handler 进行处理
func (opt *Options) WithMessageErrorHandler(handler func(srv Server, message Message, err error)) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.MessageErrorHandler = handler
opt.messageErrorHandler = handler
})
}
func (opt *Options) getMessageErrorHandler() func(srv Server, message Message, err error) {
func (opt *Options) GetMessageErrorHandler() func(srv Server, message Message, err error) {
return getOptionsValue(opt, func(opt *Options) func(srv Server, message Message, err error) {
return opt.MessageErrorHandler
return opt.messageErrorHandler
})
}
@ -133,7 +168,7 @@ func (opt *Options) getMessageErrorHandler() func(srv Server, message Message, e
// - 该函数支持运行时设置
func (opt *Options) WithLifeCycleLimit(limit time.Duration) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.LifeCycleLimit = limit
opt.lifeCycleLimit = limit
})
}
@ -144,17 +179,16 @@ func (opt *Options) WithLifeCycleEnd(end time.Time) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
now := time.Now()
if end.Before(now) {
opt.LifeCycleLimit = 0
opt.lifeCycleLimit = 0
return
}
opt.LifeCycleLimit = end.Sub(now)
opt.lifeCycleLimit = end.Sub(now)
})
}
// getLifeCycleLimit 获取服务器生命周期上限
func (opt *Options) getLifeCycleLimit() time.Duration {
func (opt *Options) GetLifeCycleLimit() time.Duration {
return getOptionsValue(opt, func(opt *Options) time.Duration {
return opt.LifeCycleLimit
return opt.lifeCycleLimit
})
}
@ -165,6 +199,12 @@ func (opt *Options) modifyOptionsValue(handler func(opt *Options)) *Options {
return opt
}
func (opt *Options) getManyOptions(handler func(opt *Options)) {
opt.rw.RLock()
defer opt.rw.RUnlock()
handler(opt)
}
func getOptionsValue[V any](opt *Options, handler func(opt *Options) V) V {
opt.rw.RLock()
defer opt.rw.RUnlock()

View File

@ -1,6 +1,7 @@
package server
import (
"errors"
"fmt"
"net/http"
"net/http/pprof"
@ -12,8 +13,8 @@ var (
httpPProfMutex sync.Mutex // HTTP PProf 服务器互斥锁
)
// EnableHttpPProf 设置启用 http pprof
// - 该函数支持运行时调用
// EnableHttpPProf 设置启用 HTTP PProf
// - 该函数支持运行时调用且支持重复调用,重复调用不会重复开启
func EnableHttpPProf(addr, prefix string, errorHandler func(err error)) {
httpPProfMutex.Lock()
defer httpPProfMutex.Unlock()
@ -36,14 +37,15 @@ func EnableHttpPProf(addr, prefix string, errorHandler func(err error)) {
srv := &http.Server{Addr: addr, Handler: mux}
httpPProf = srv
go func(srv *http.Server, errHandler func(err error)) {
if err := srv.ListenAndServe(); err != nil {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errorHandler(err)
}
}(srv, errorHandler)
}
// DisableHttpPProf 设置禁用 http pprof
// - 该函数支持运行时调用
// DisableHttpPProf 设置禁用 HTTP PProf
// - 当 HTTP PProf 未启用时不会产生任何效果
// - 该函数支持运行时调用且支持重复调用,重复调用不会重复禁用
func DisableHttpPProf() {
httpPProfMutex.Lock()
defer httpPProfMutex.Unlock()

View File

@ -4,9 +4,7 @@ import (
"fmt"
"github.com/alphadose/haxmap"
"github.com/kercylan98/minotaur/server/internal/v2/loadbalancer"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/super"
"log/slog"
"runtime"
"runtime/debug"
"sync"
@ -25,7 +23,6 @@ var sysIdent = &identifiable{ident: "system"}
// NewReactor 创建一个新的 Reactor 实例,初始化系统级别的队列和多个 Socket 对应的队列
func NewReactor[M any](systemQueueSize, queueSize, systemBufferSize, queueBufferSize int, handler MessageHandler[M], errorHandler ErrorHandler[M]) *Reactor[M] {
r := &Reactor[M]{
logger: log.Default().Logger,
systemQueue: newQueue[M](-1, systemQueueSize, systemBufferSize),
identifiers: haxmap.New[string, *identifiable](),
lb: loadbalancer.NewRoundRobin[int, *queue[M]](),
@ -80,7 +77,6 @@ func NewReactor[M any](systemQueueSize, queueSize, systemBufferSize, queueBuffer
// Reactor 是一个消息反应器,管理系统级别的队列和多个 Socket 对应的队列
type Reactor[M any] struct {
logger *slog.Logger // 日志记录器
state int32 // 状态
systemQueue *queue[M] // 系统级别的队列
queueSize int // 队列管道大小
@ -93,13 +89,6 @@ type Reactor[M any] struct {
cwg sync.WaitGroup // 关闭等待组
handler queueMessageHandler[M] // 消息处理器
errorHandler ErrorHandler[M] // 错误处理器
debug bool // 是否开启调试模式
}
// SetDebug 设置是否开启调试模式
func (r *Reactor[M]) SetDebug(debug bool) *Reactor[M] {
r.debug = debug
return r
}
// AutoDispatch 自动分发,当 ident 为空字符串时,分发到系统级别的队列,否则分发到 ident 使用的队列

View File

@ -2,6 +2,7 @@ package server
import (
"context"
"github.com/kercylan98/minotaur/utils/log/v2"
"time"
"github.com/kercylan98/minotaur/server/internal/v2/reactor"
@ -44,12 +45,12 @@ func NewServer(network Network, options ...*Options) Server {
srv.events = new(events).init(srv)
srv.state = new(State).init(srv)
srv.reactor = reactor.NewReactor[Message](
srv.getServerMessageChannelSize(), srv.getActorMessageChannelSize(),
srv.getServerMessageBufferInitialSize(), srv.getActorMessageBufferInitialSize(),
srv.GetServerMessageChannelSize(), srv.GetActorMessageChannelSize(),
srv.GetServerMessageBufferInitialSize(), srv.GetActorMessageBufferInitialSize(),
func(msg Message) {
msg.Execute()
}, func(msg Message, err error) {
if handler := srv.getMessageErrorHandler(); handler != nil {
if handler := srv.GetMessageErrorHandler(); handler != nil {
handler(srv, msg, err)
}
})
@ -77,7 +78,13 @@ func (s *server) Run() (err error) {
}
func (s *server) Shutdown() (err error) {
s.GetLogger().Info("Minotaur Server", log.String("", "ShutdownInfo"), log.String("state", "start"))
defer func(startAt time.Time) {
s.GetLogger().Info("Minotaur Server", log.String("", "ShutdownInfo"), log.String("state", "done"), log.String("cost", time.Since(startAt).String()))
}(time.Now())
defer s.cancel()
DisableHttpPProf()
s.events.onShutdown()
err = s.network.OnShutdown()
s.reactor.Close()

View File

@ -14,16 +14,17 @@ func TestNewServer(t *testing.T) {
panic(err)
})
go func() {
time.Sleep(time.Second * 5)
server.DisableHttpPProf()
time.Sleep(time.Second * 5)
server.EnableHttpPProf(":9998", "/debug/pprof", func(err error) {
panic(err)
})
}()
srv := server.NewServer(network.WebSocket(":9999"), server.NewOptions().WithLifeCycleLimit(times.Second*3))
srv.RegisterLaunchedEvent(func(srv server.Server, ip string, launchedAt time.Time) {
t.Log("launched", ip, launchedAt)
})
srv.RegisterShutdownEvent(func(srv server.Server) {
t.Log("shutdown")
})
srv.RegisterConnectionOpenedEvent(func(srv server.Server, conn server.Conn) {
if err := conn.WritePacket(server.NewPacket([]byte("hello")).SetContext(ws.OpText)); err != nil {
t.Error(err)

85
utils/log/v2/color.go Normal file
View File

@ -0,0 +1,85 @@
package log
import "github.com/fatih/color"
// Base attributes
const (
ColorReset Attribute = iota
ColorBold
ColorFaint
ColorItalic
ColorUnderline
ColorBlinkSlow
ColorBlinkRapid
ColorReverseVideo
ColorConcealed
ColorCrossedOut
)
const (
ColorResetBold Attribute = iota + 22
ColorResetItalic
ColorResetUnderline
ColorResetBlinking
_
ColorResetReversed
ColorResetConcealed
ColorResetCrossedOut
)
// Foreground text colors
const (
ColorFgBlack Attribute = iota + 30
ColorFgRed
ColorFgGreen
ColorFgYellow
ColorFgBlue
ColorFgMagenta
ColorFgCyan
ColorFgWhite
)
// Foreground Hi-Intensity text colors
const (
ColorFgHiBlack Attribute = iota + 90
ColorFgHiRed
ColorFgHiGreen
ColorFgHiYellow
ColorFgHiBlue
ColorFgHiMagenta
ColorFgHiCyan
ColorFgHiWhite
)
// Background text colors
const (
ColorBgBlack Attribute = iota + 40
ColorBgRed
ColorBgGreen
ColorBgYellow
ColorBgBlue
ColorBgMagenta
ColorBgCyan
ColorBgWhite
)
// Background Hi-Intensity text colors
const (
ColorBgHiBlack Attribute = iota + 100
ColorBgHiRed
ColorBgHiGreen
ColorBgHiYellow
ColorBgHiBlue
ColorBgHiMagenta
ColorBgHiCyan
ColorBgHiWhite
)
func NewColor(attributes ...Attribute) *Color {
c := &Color{
Color: color.New(attributes...),
attrs: attributes,
}
c.Color.EnableColor()
return c
}

56
utils/log/v2/defines.go Normal file
View File

@ -0,0 +1,56 @@
package log
import (
"github.com/fatih/color"
"log/slog"
)
type (
Level = slog.Level
Handler = slog.Handler
Logger = slog.Logger
Field = slog.Attr
)
type (
Attribute = color.Attribute
Color struct {
*color.Color
attrs []Attribute
}
)
type AttrType int
const (
AttrTypeTime AttrType = iota + 1
AttrTypeCaller
AttrTypeMessage
AttrTypeField
AttrTypeTrace
AttrTypeError
)
const (
levelNone Level = slog.LevelDebug - 1
LevelDebug Level = slog.LevelDebug
LevelInfo Level = slog.LevelInfo
LevelWarn Level = slog.LevelWarn
LevelError Level = slog.LevelError
)
var levels = []Level{LevelDebug, LevelInfo, LevelWarn, LevelError}
func (c *Color) Add(value ...Attribute) *Color {
c.Color.Add(value...)
c.attrs = append(c.attrs, value...)
return c
}
func (c *Color) clone() *Color {
return NewColor(c.attrs...)
}
func Levels() []Level {
return levels
}

255
utils/log/v2/field.go Normal file
View File

@ -0,0 +1,255 @@
package log
import (
"github.com/kercylan98/minotaur/utils/generic"
"github.com/pkg/errors"
"log/slog"
"time"
)
// Skip 构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用
// - 该函数还支持将其他字段快捷的转换为 Skip 字段
func Skip(vs ...any) Field {
return Field{Key: ""}
}
// Duration 使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式
func Duration(key string, val time.Duration) Field {
return slog.String(key, val.String())
}
// DurationP 构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func DurationP(key string, val *time.Duration) Field {
if val == nil {
return slog.Any(key, nil)
}
return Duration(key, *val)
}
// Bool 构造一个带有布尔值的字段
func Bool(key string, val bool) Field {
return slog.Bool(key, val)
}
// BoolP 构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func BoolP(key string, val *bool) Field {
if val == nil {
return slog.Any(key, nil)
}
return Bool(key, *val)
}
// String 构造一个带有字符串值的字段
func String(key, val string) Field {
return slog.String(key, val)
}
// StringP 构造一个带有字符串值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func StringP(key string, val *string) Field {
if val == nil {
return slog.Any(key, nil)
}
return String(key, *val)
}
// Int 构造一个带有整数值的字段
func Int[I generic.Integer](key string, val I) Field {
return slog.Int(key, int(val))
}
// IntP 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func IntP[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Int(key, *val)
}
// Int8 构造一个带有整数值的字段
func Int8[I generic.Integer](key string, val I) Field {
return slog.Int(key, int(val))
}
// Int8P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Int8P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Int8(key, *val)
}
// Int16 构造一个带有整数值的字段
func Int16[I generic.Integer](key string, val I) Field {
return slog.Int(key, int(val))
}
// Int16P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Int16P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Int16(key, *val)
}
// Int32 构造一个带有整数值的字段
func Int32[I generic.Integer](key string, val I) Field {
return slog.Int(key, int(val))
}
// Int32P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Int32P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Int32(key, *val)
}
// Int64 构造一个带有整数值的字段
func Int64[I generic.Integer](key string, val I) Field {
return slog.Int64(key, int64(val))
}
// Int64P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Int64P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Int64(key, *val)
}
// Uint 构造一个带有整数值的字段
func Uint[I generic.Integer](key string, val I) Field {
return slog.Uint64(key, uint64(val))
}
// UintP 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func UintP[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Uint(key, *val)
}
// Uint8 构造一个带有整数值的字段
func Uint8[I generic.Integer](key string, val I) Field {
return slog.Uint64(key, uint64(val))
}
// Uint8P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Uint8P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Uint8(key, *val)
}
// Uint16 构造一个带有整数值的字段
func Uint16[I generic.Integer](key string, val I) Field {
return slog.Uint64(key, uint64(val))
}
// Uint16P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Uint16P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Uint16(key, *val)
}
// Uint32 构造一个带有整数值的字段
func Uint32[I generic.Integer](key string, val I) Field {
return slog.Uint64(key, uint64(val))
}
// Uint32P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Uint32P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Uint32(key, *val)
}
// Uint64 构造一个带有整数值的字段
func Uint64[I generic.Integer](key string, val I) Field {
return slog.Uint64(key, uint64(val))
}
// Uint64P 构造一个带有整数值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Uint64P[I generic.Integer](key string, val *I) Field {
if val == nil {
return slog.Any(key, nil)
}
return Uint64(key, *val)
}
// Float 构造一个带有浮点值的字段
func Float[F generic.Float](key string, val F) Field {
return slog.Float64(key, float64(val))
}
// FloatP 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func FloatP[F generic.Float](key string, val *F) Field {
if val == nil {
return slog.Any(key, nil)
}
return Float(key, *val)
}
// Float32 构造一个带有浮点值的字段
func Float32[F generic.Float](key string, val F) Field {
return slog.Float64(key, float64(val))
}
// Float32P 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Float32P[F generic.Float](key string, val *F) Field {
if val == nil {
return slog.Any(key, nil)
}
return Float32(key, *val)
}
// Float64 构造一个带有浮点值的字段
func Float64[F generic.Float](key string, val F) Field {
return slog.Float64(key, float64(val))
}
// Float64P 构造一个带有浮点值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func Float64P[F generic.Float](key string, val *F) Field {
if val == nil {
return slog.Any(key, nil)
}
return Float64(key, *val)
}
// Time 构造一个带有时间值的字段
func Time(key string, val time.Time) Field {
return slog.Time(key, val)
}
// TimeP 构造一个带有时间值的字段。返回的 Field 将在适当的时候安全且显式地表示 "null"
func TimeP(key string, val *time.Time) Field {
if val == nil {
return slog.Any(key, nil)
}
return Time(key, *val)
}
// Any 构造一个带有任意值的字段
func Any(key string, val any) Field {
return slog.Any(key, val)
}
// Group 返回分组字段
func Group(key string, args ...any) Field {
return slog.Group(key, args...)
}
// Stack 返回堆栈字段
func Stack(key string) Field {
return slog.Any(key, errors.New(""))
}
// Err 构造一个带有错误值的字段
func Err(err error) Field {
return slog.Any("error", err)
}

340
utils/log/v2/handler.go Normal file
View File

@ -0,0 +1,340 @@
package log
import (
"context"
"encoding"
"fmt"
"io"
"log/slog"
"runtime"
"strconv"
"strings"
"sync"
"unicode"
"github.com/kercylan98/minotaur/utils/str"
)
func NewHandler(w io.Writer, opts ...*Options) *MinotaurHandler {
h := &MinotaurHandler{
opts: DefaultOptions(),
w: w,
}
for _, opt := range opts {
h.opts.Apply(opt)
}
return h
}
type MinotaurHandler struct {
opts *Options
groupPrefix string
groups []string
mu sync.Mutex
w io.Writer
}
func (h *MinotaurHandler) GetOptions() *Options {
return h.opts
}
func (h *MinotaurHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.opts.GetLevel()
}
func (h *MinotaurHandler) Handle(ctx context.Context, record slog.Record) (err error) {
h.opts.getMany(func(opt *Options) {
if !h.Enabled(ctx, opt.level) {
return
}
var buffer = new(strings.Builder)
defer buffer.Reset()
processTime(buffer, record, opt)
processLevel(buffer, record, opt)
processCaller(buffer, record, opt)
processMessage(buffer, record, opt)
processAttrs(buffer, h, record, opt, record.Level, h.groupPrefix, h.groups)
if buffer.Len() == 0 {
return
}
buffer.WriteByte('\n')
h.mu.Lock()
defer h.mu.Unlock()
_, err = h.w.Write([]byte(buffer.String()))
return
})
return
}
func (h *MinotaurHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
handler := h.clone()
buffer := new(strings.Builder)
defer buffer.Reset()
h.opts.getMany(func(opt *Options) {
for _, attr := range attrs {
processAttrsAttr(buffer, h, attr, opt, levelNone, h.groupPrefix, h.groups)
}
})
//MinotaurHandler.opts.w = h.opts.FieldPrefix + string(*buf.bytes)
return handler
}
func (h *MinotaurHandler) WithGroup(name string) slog.Handler {
if name == "" {
return h
}
handler := h.clone()
handler.groupPrefix += name + "."
handler.groups = append(handler.groups, name)
return handler
}
func (h *MinotaurHandler) clone() *MinotaurHandler {
return &MinotaurHandler{
groupPrefix: h.groupPrefix,
opts: DefaultOptions().Apply(h.opts),
groups: h.groups,
w: h.w,
}
}
func processTime(buffer *strings.Builder, record slog.Record, opt *Options) {
if record.Time.IsZero() {
return
}
processAttrType(buffer, opt, AttrTypeTime, record.Time.Format(opt.timeLayout))
}
func processLevel(buffer *strings.Builder, record slog.Record, opt *Options) {
var levelColor = opt.levelColor[record.Level]
var levelText = opt.levelText[record.Level]
if levelText == str.None {
return
}
if opt.disabledColor || levelColor == nil {
buffer.WriteString(levelText)
} else {
buffer.WriteString(levelColor.Sprint(levelText))
}
buffer.WriteByte(' ')
}
func processCaller(buffer *strings.Builder, record slog.Record, opt *Options) {
if opt.disabledCaller {
return
}
pcs := make([]uintptr, 1)
runtime.CallersFrames(pcs[:runtime.Callers(opt.callerSkip, pcs)])
fs := runtime.CallersFrames(pcs)
f, _ := fs.Next()
if f.File == str.None {
return
}
file, line := opt.callerFormatter(f.File, f.Line)
processAttrType(buffer, opt, AttrTypeCaller, file+":"+line)
}
func processMessage(buffer *strings.Builder, record slog.Record, opt *Options) {
processAttrType(buffer, opt, AttrTypeMessage, record.Message)
}
func processAttrs(buffer *strings.Builder, handler *MinotaurHandler, record slog.Record, opt *Options, level Level, groupsPrefix string, groups []string) {
record.Attrs(func(attr slog.Attr) bool {
processAttrsAttr(buffer, handler, attr, opt, level, groupsPrefix, groups)
return true
})
}
func processAttrsAttr(buffer *strings.Builder, handler *MinotaurHandler, attr slog.Attr, opt *Options, level Level, groupsPrefix string, groups []string) {
attr.Value = attr.Value.Resolve()
if attr.Equal(slog.Attr{}) {
return
}
switch attr.Value.Kind() {
case slog.KindGroup:
if attr.Key != "" {
groupsPrefix += attr.Key + "."
groups = append(groups, attr.Key)
}
for _, groupAttr := range attr.Value.Group() {
processAttrsAttr(buffer, handler, groupAttr, opt, level, groupsPrefix, groups)
}
default:
switch v := attr.Value.Any().(type) {
case error:
if opt.stackTrace[level] {
stackTraceAttr := slog.Attr{Key: attr.Key, Value: formatTraceError(v, opt.stackTraceBeauty[level])}
processAttrsAttr(buffer, handler, stackTraceAttr, opt, level, handler.groupPrefix, handler.groups)
return
}
color := opt.keyColor[AttrTypeError]
if opt.disabledColor || color == nil {
processAttrsKey(buffer, opt, attr.Key, groupsPrefix)
} else {
processAttrsKey(buffer, opt, attr.Key, groupsPrefix, color)
}
processAttrsValue(buffer, opt, attr.Value, true)
buffer.WriteByte(' ')
case *beautyTrace:
color := opt.valueColor[AttrTypeTrace]
for _, s := range v.trace {
buffer.WriteString("\n\t")
if opt.disabledColor || color == nil {
buffer.WriteString(s)
} else {
buffer.WriteString(color.Sprint(s))
}
}
default:
processAttrsKey(buffer, opt, attr.Key, groupsPrefix)
processAttrsValue(buffer, opt, attr.Value, true)
buffer.WriteByte(' ')
}
}
}
func processAttrsString(s string, quote bool) string {
quoting := len(s) == 0
for _, r := range s {
if unicode.IsSpace(r) || r == '"' || r == '=' || !unicode.IsPrint(r) {
quoting = true
break
}
}
if quote && quoting {
return strconv.Quote(s)
} else {
return s
}
}
func processAttrsKey(buffer *strings.Builder, opt *Options, key, groups string, replaceColor ...*Color) {
if key == str.None {
return
}
color := opt.keyColor[AttrTypeField]
if len(replaceColor) > 0 {
color = replaceColor[0]
}
if opt.disabledColor || color == nil {
buffer.WriteString(processAttrsString(groups+key, true))
} else {
buffer.WriteString(color.Sprint(processAttrsString(groups+key, true)))
}
delimiterText := opt.delimiterText[AttrTypeField]
if delimiterText != str.None {
delimiterColor := opt.delimiterColor[AttrTypeField]
if len(replaceColor) > 1 {
delimiterColor = replaceColor[1]
}
if opt.disabledColor || delimiterColor == nil {
buffer.WriteString(delimiterText)
} else {
buffer.WriteString(delimiterColor.Sprint(delimiterText))
}
}
}
func processAttrsValue(buffer *strings.Builder, opt *Options, v slog.Value, quote bool) {
var text string
var color = opt.valueColor[AttrTypeField]
switch v.Kind() {
case slog.KindString:
text = processAttrsString(v.String(), quote)
case slog.KindInt64:
text = strconv.FormatInt(v.Int64(), 10)
case slog.KindUint64:
text = strconv.FormatUint(v.Uint64(), 10)
case slog.KindFloat64:
text = strconv.FormatFloat(v.Float64(), 'g', -1, 64)
case slog.KindBool:
text = strconv.FormatBool(v.Bool())
case slog.KindDuration:
text = processAttrsString(v.Duration().String(), quote)
case slog.KindTime:
text = processAttrsString(v.Time().String(), quote)
case slog.KindAny:
switch cv := v.Any().(type) {
case slog.Level:
processLevel(buffer, slog.Record{Level: cv}, opt)
case encoding.TextMarshaler:
data, err := cv.MarshalText()
if err != nil {
break
}
text = processAttrsString(string(data), quote)
case *slog.Source:
file, line := opt.callerFormatter(cv.File, cv.Line)
callerColor := opt.valueColor[AttrTypeCaller]
if opt.disabledColor || callerColor == nil {
buffer.WriteString(file + ":" + line)
} else {
buffer.WriteString(callerColor.Sprintf("%s:%s", file, line))
}
buffer.WriteByte(' ')
default:
text = processAttrsString(fmt.Sprint(v.Any()), quote)
}
default:
}
if text == str.None {
return
}
if opt.disabledColor || color == nil {
buffer.WriteString(text)
} else {
buffer.WriteString(color.Sprint(text))
}
}
func processAttrType(buffer *strings.Builder, opt *Options, attrType AttrType, value string) {
prefixText := opt.keyText[attrType]
prefixColor := opt.keyColor[attrType]
delimiterText := opt.delimiterText[attrType]
delimiterColor := opt.delimiterColor[attrType]
valueColor := opt.valueColor[attrType]
if prefixText != str.None {
// 前缀
if opt.disabledColor || prefixColor == nil {
buffer.WriteString(prefixText)
} else {
buffer.WriteString(prefixColor.Sprint(prefixText))
}
// 分隔符
if delimiterText != str.None {
if opt.disabledColor || delimiterColor == nil {
buffer.WriteString(delimiterText)
} else {
buffer.WriteString(delimiterColor.Sprint(delimiterText))
}
}
}
// 时间信息
if opt.disabledColor || valueColor == nil {
buffer.WriteString(value)
} else {
buffer.WriteString(valueColor.Sprint(value))
}
buffer.WriteByte(' ')
}

93
utils/log/v2/logger.go Normal file
View File

@ -0,0 +1,93 @@
package log
import (
"log/slog"
"os"
"sync/atomic"
)
var logger = func() *atomic.Pointer[Logger] {
var p atomic.Pointer[Logger]
p.Store(slog.New(NewHandler(os.Stdout)))
return &p
}()
// NewLogger 创建一个新的日志记录器
func NewLogger(handler Handler) *Logger {
return slog.New(handler)
}
// NewDefaultLogger 创建一个新的默认日志记录器
func NewDefaultLogger() *Logger {
return NewLogger(NewHandler(os.Stdout))
}
// GetLogger 并发安全的获取当前全局日志记录器
func GetLogger() *Logger {
l := logger.Load()
if h := cloneHandler(l.Handler()); h != nil {
return NewLogger(h)
}
return l
}
// SetLogger 并发安全的设置全局日志记录器
func SetLogger(l *Logger) {
logger.Store(l)
}
// ResetLogger 并发安全的重置全局日志记录器
func ResetLogger() {
logger.Store(slog.New(NewHandler(os.Stdout)))
}
// Debug 使用全局日志记录器在 LevelDebug 级别下记录一条消息
func Debug(msg string, args ...any) {
logger.Load().Debug(msg, args...)
}
// Info 使用全局日志记录器在 LevelInfo 级别下记录一条消息
func Info(msg string, args ...any) {
logger.Load().Info(msg, args...)
}
// Warn 使用全局日志记录器在 LevelWarn 级别下记录一条消息
func Warn(msg string, args ...any) {
logger.Load().Warn(msg, args...)
}
// Error 使用全局日志记录器在 LevelError 级别下记录一条消息
func Error(msg string, args ...any) {
logger.Load().Error(msg, args...)
}
// Log 按照指定级别记录日志消息
func Log(level Level, msg string, args ...any) {
switch level {
case LevelDebug:
logger.Load().Debug(msg, args...)
case LevelInfo:
logger.Load().Info(msg, args...)
case LevelWarn:
logger.Load().Warn(msg, args...)
case LevelError:
logger.Load().Error(msg, args...)
default:
}
}
func cloneHandler(h Handler) Handler {
switch h := h.(type) {
case *MinotaurHandler:
cloneHandler := h.clone()
cloneHandler.GetOptions().WithCallerSkip(-1)
return cloneHandler
case *MultiHandler:
var handlers = make([]Handler, 0, len(h.handlers))
for _, handler := range h.handlers {
handlers = append(handlers, cloneHandler(handler))
}
return NewMultiHandler(handlers...)
}
return nil
}

View File

@ -0,0 +1,19 @@
package log_test
import (
"errors"
"github.com/kercylan98/minotaur/utils/log/v2"
"testing"
)
func TestLogger(t *testing.T) {
var msg = "TestLogger"
var fields = make([]any, 0)
fields = append(fields, log.String("Name", "Jerry"), log.Any("errhhha", errors.New("test error")), log.Err(errors.New("test error")))
for _, level := range log.Levels() {
log.Log(level, msg, fields...)
}
}

View File

@ -0,0 +1,62 @@
package log
import (
"context"
"github.com/kercylan98/minotaur/utils/super"
"log/slog"
)
// NewMultiHandler 创建一个新的多处理程序
func NewMultiHandler(handlers ...slog.Handler) slog.Handler {
return &MultiHandler{
handlers: handlers,
}
}
type MultiHandler struct {
handlers []slog.Handler
}
func (h MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
for i := range h.handlers {
if h.handlers[i].Enabled(ctx, level) {
return true
}
}
return false
}
func (h MultiHandler) Handle(ctx context.Context, record slog.Record) (err error) {
for i := range h.handlers {
if h.handlers[i].Enabled(ctx, record.Level) {
err = func() error {
defer func() {
err = super.RecoverTransform(recover())
}()
return h.handlers[i].Handle(ctx, record.Clone())
}()
if err != nil {
return err
}
}
}
return nil
}
func (h MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
var handlers = make([]slog.Handler, len(h.handlers))
for i, s := range h.handlers {
handlers[i] = s.WithAttrs(attrs)
}
return NewMultiHandler(handlers...)
}
func (h MultiHandler) WithGroup(name string) slog.Handler {
var handlers = make([]slog.Handler, len(h.handlers))
for i, s := range h.handlers {
handlers[i] = s.WithGroup(name)
}
return NewMultiHandler(handlers...)
}

381
utils/log/v2/options.go Normal file
View File

@ -0,0 +1,381 @@
package log
import (
"github.com/kercylan98/minotaur/utils/collection"
"path/filepath"
"strconv"
"sync"
"time"
)
const basicCallSkip = 8
func DefaultOptions() *Options {
return &Options{
timeLayout: time.DateTime,
callerFormatter: func(file string, line int) (repFile, repLine string) {
return filepath.Base(file), strconv.Itoa(line)
},
level: LevelDebug,
levelText: map[Level]string{
LevelDebug: "DBG",
LevelInfo: "INF",
LevelWarn: "WAR",
LevelError: "ERR",
},
levelColor: map[Level]*Color{
LevelDebug: NewColor(ColorFgCyan),
LevelInfo: NewColor(ColorFgGreen),
LevelWarn: NewColor(ColorFgHiYellow),
LevelError: NewColor(ColorFgHiRed),
},
callerSkip: basicCallSkip,
keyColor: map[AttrType]*Color{
AttrTypeField: NewColor(ColorFgWhite),
AttrTypeError: NewColor(ColorFgRed),
},
delimiterText: map[AttrType]string{
AttrTypeField: "=",
},
valueColor: map[AttrType]*Color{
AttrTypeCaller: NewColor(ColorFgHiCyan),
AttrTypeMessage: NewColor(ColorFgWhite, ColorBold),
AttrTypeTrace: NewColor(ColorFgWhite, ColorFaint),
},
}
}
type Options struct {
rw sync.RWMutex
level Level // 日志级别
timeLayout string
keyText map[AttrType]string // 特定属性类型的前缀字符串
keyColor map[AttrType]*Color // 特定属性类型的前缀颜色
delimiterText map[AttrType]string // 特定属性类型的分隔符字符串
delimiterColor map[AttrType]*Color // 特定属性类型的分隔符颜色
valueColor map[AttrType]*Color // 特定属性类型的值颜色
levelText map[Level]string // 特定级别的字符串
levelColor map[Level]*Color // 特定级别的颜色
disabledColor bool // 是否禁用颜色
callerSkip int // 调用者跳过数量
disabledCaller bool // 是否禁用调用者
callerFormatter func(file string, line int) (repFile, repLine string) // 调用者格式化函数
stackTrace map[Level]bool // 是否开启特定级别的堆栈追踪
stackTraceBeauty map[Level]bool // 是否开启特定级别的堆栈追踪美化
}
func (opt *Options) Apply(opts ...*Options) *Options {
opt.rw.Lock()
defer opt.rw.Unlock()
for _, o := range opts {
o.getMany(func(o *Options) {
opt.level = o.level
opt.keyText = collection.CloneMap(o.keyText)
opt.delimiterText = collection.CloneMap(o.delimiterText)
opt.keyColor = map[AttrType]*Color{}
for attrType, color := range o.keyColor {
opt.keyColor[attrType] = color.clone()
}
opt.delimiterColor = map[AttrType]*Color{}
for attrType, color := range o.delimiterColor {
opt.delimiterColor[attrType] = color.clone()
}
opt.valueColor = map[AttrType]*Color{}
for attrType, color := range o.valueColor {
opt.valueColor[attrType] = color.clone()
}
opt.timeLayout = o.timeLayout
opt.disabledColor = o.disabledColor
opt.levelColor = make(map[Level]*Color)
for level, color := range o.levelColor {
opt.levelColor[level] = color.clone()
}
opt.callerSkip = o.callerSkip
opt.disabledCaller = o.disabledCaller
opt.levelText = make(map[Level]string)
for level, text := range o.levelText {
opt.levelText[level] = text
}
opt.callerFormatter = o.callerFormatter
opt.stackTrace = collection.CloneMap(o.stackTrace)
opt.stackTraceBeauty = collection.CloneMap(o.stackTraceBeauty)
})
}
return opt
}
// WithStackTraceBeauty 设置堆栈追踪美化
// - 该函数支持运行时设置
func (opt *Options) WithStackTraceBeauty(level Level, enable bool) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.stackTraceBeauty == nil {
opt.stackTraceBeauty = make(map[Level]bool)
}
opt.stackTraceBeauty[level] = enable
})
}
// GetStackTraceBeauty 获取堆栈追踪是否美化
func (opt *Options) GetStackTraceBeauty(level Level) bool {
return getOptionsValue(opt, func(opt *Options) bool {
return opt.stackTraceBeauty[level]
})
}
// WithStackTrace 设置堆栈追踪,当日志记录器中包含 error 时,将会打印堆栈信息
// - 该函数支持运行时设置
func (opt *Options) WithStackTrace(level Level, enable bool) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.stackTrace == nil {
opt.stackTrace = make(map[Level]bool)
}
opt.stackTrace[level] = enable
})
}
// GetStackTrace 获取堆栈追踪
func (opt *Options) GetStackTrace(level Level) bool {
return getOptionsValue(opt, func(opt *Options) bool {
return opt.stackTrace[level]
})
}
// WithCallerFormatter 设置调用者格式化函数
// - 该函数支持运行时设置
func (opt *Options) WithCallerFormatter(formatter func(file string, line int) (repFile, repLine string)) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.callerFormatter = formatter
})
}
// GetCallerFormatter 获取调用者格式化函数
func (opt *Options) GetCallerFormatter() func(file string, line int) (repFile, repLine string) {
return getOptionsValue(opt, func(opt *Options) func(file string, line int) (repFile, repLine string) {
return opt.callerFormatter
})
}
// WithDisableCaller 设置是否禁用调用者
// - 该函数支持运行时设置
func (opt *Options) WithDisableCaller(disable bool) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.disabledCaller = disable
})
}
// IsDisabledCaller 获取是否已经禁用调用者
func (opt *Options) IsDisabledCaller() bool {
return getOptionsValue(opt, func(opt *Options) bool {
return opt.disabledCaller
})
}
// WithAttrPrefix 设置属性前缀
// - 该函数支持运行时设置
func (opt *Options) WithAttrPrefix(attrType AttrType, prefix string) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.keyText == nil {
opt.keyText = map[AttrType]string{}
}
opt.keyText[attrType] = prefix
})
}
// GetAttrPrefix 获取属性前缀
func (opt *Options) GetAttrPrefix(attrType AttrType) string {
return getOptionsValue(opt, func(opt *Options) string {
return opt.keyText[attrType]
})
}
// WithAttrDelimiter 设置属性分隔符
// - 该函数支持运行时设置
func (opt *Options) WithAttrDelimiter(attrType AttrType, delimiter string) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.delimiterText == nil {
opt.delimiterText = map[AttrType]string{}
}
opt.delimiterText[attrType] = delimiter
})
}
// GetAttrDelimiter 获取属性分隔符
func (opt *Options) GetAttrDelimiter(attrType AttrType) string {
return getOptionsValue(opt, func(opt *Options) string {
return opt.delimiterText[attrType]
})
}
// WithAttrPrefixColor 设置属性前缀颜色
// - 该函数支持运行时设置
func (opt *Options) WithAttrPrefixColor(attrType AttrType, color *Color) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.keyColor == nil {
opt.keyColor = map[AttrType]*Color{}
}
opt.keyColor[attrType] = color
})
}
// GetAttrPrefixColor 获取属性前缀颜色
func (opt *Options) GetAttrPrefixColor(attrType AttrType) *Color {
return getOptionsValue(opt, func(opt *Options) *Color {
return opt.keyColor[attrType]
})
}
// WithAttrDelimiterColor 设置属性分隔符颜色
// - 该函数支持运行时设置
func (opt *Options) WithAttrDelimiterColor(attrType AttrType, color *Color) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.delimiterColor == nil {
opt.delimiterColor = map[AttrType]*Color{}
}
opt.delimiterColor[attrType] = color
})
}
// GetAttrDelimiterColor 获取属性分隔符颜色
func (opt *Options) GetAttrDelimiterColor(attrType AttrType) *Color {
return getOptionsValue(opt, func(opt *Options) *Color {
return opt.delimiterColor[attrType]
})
}
// WithAttrTextColor 设置属性文本颜色
// - 该函数支持运行时设置
func (opt *Options) WithAttrTextColor(attrType AttrType, color *Color) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.valueColor == nil {
opt.valueColor = map[AttrType]*Color{}
}
opt.valueColor[attrType] = color
})
}
// GetAttrTextColor 获取属性前缀颜色
func (opt *Options) GetAttrTextColor(attrType AttrType) *Color {
return getOptionsValue(opt, func(opt *Options) *Color {
return opt.valueColor[attrType]
})
}
// WithCallerSkip 设置调用者跳过数量
// - 该函数支持运行时设置
func (opt *Options) WithCallerSkip(skip int) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.callerSkip = basicCallSkip + skip
})
}
// GetCallerSkip 获取调用者跳过数量
func (opt *Options) GetCallerSkip() int {
return getOptionsValue(opt, func(opt *Options) int {
return basicCallSkip - opt.callerSkip
})
}
// WithLevelText 设置日志级别文本
// - 该函数支持运行时设置
func (opt *Options) WithLevelText(level Level, text string) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.levelText == nil {
opt.levelText = make(map[Level]string)
}
opt.levelText[level] = text
})
}
// GetLevelText 获取日志级别文本
func (opt *Options) GetLevelText(level Level) string {
return getOptionsValue(opt, func(opt *Options) string {
return opt.levelText[level]
})
}
// WithLevelColor 设置日志级别颜色
// - 该函数支持运行时设置
func (opt *Options) WithLevelColor(level Level, color *Color) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
if opt.levelColor == nil {
opt.levelColor = make(map[Level]*Color)
}
opt.levelColor[level] = color
})
}
// GetLevelColor 获取日志级别颜色
func (opt *Options) GetLevelColor(level Level) *Color {
return getOptionsValue(opt, func(opt *Options) *Color {
return opt.levelColor[level]
})
}
// WithDisableColor 设置禁用颜色
// - 该函数支持运行时设置
func (opt *Options) WithDisableColor(disable bool) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.disabledColor = disable
})
}
// IsDisabledColor 获取是否已经禁用颜色
func (opt *Options) IsDisabledColor() bool {
return getOptionsValue(opt, func(opt *Options) bool {
return opt.disabledColor
})
}
// WithLevel 设置日志级别
// - 该函数支持运行时设置
func (opt *Options) WithLevel(level Level) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.level = level
})
}
// GetLevel 获取当前日志级别
func (opt *Options) GetLevel() Level {
return getOptionsValue(opt, func(opt *Options) Level {
return opt.level
})
}
// WithTimeLayout 设置日志的时间布局,默认为 time.DateTime
// - 该函数支持运行时设置
func (opt *Options) WithTimeLayout(layout string) *Options {
return opt.modifyOptionsValue(func(opt *Options) {
opt.timeLayout = layout
})
}
// GetTimeLayout 获取当前日志的时间布局
func (opt *Options) GetTimeLayout() string {
return getOptionsValue(opt, func(opt *Options) string {
return opt.timeLayout
})
}
func (opt *Options) modifyOptionsValue(handler func(opt *Options)) *Options {
opt.rw.Lock()
handler(opt)
opt.rw.Unlock()
return opt
}
func (opt *Options) getMany(handler func(opt *Options)) {
opt.rw.RLock()
defer opt.rw.RUnlock()
handler(opt)
}
func getOptionsValue[V any](opt *Options, handler func(opt *Options) V) V {
opt.rw.RLock()
defer opt.rw.RUnlock()
return handler(opt)
}

View File

@ -0,0 +1,80 @@
package log
import (
"fmt"
"github.com/pkg/errors"
"log/slog"
"runtime"
"strings"
)
type beautyTrace struct {
trace []string
}
func formatTraceError(err error, beauty bool) slog.Value {
var groupValues []slog.Attr
if msg := err.Error(); msg != "" {
groupValues = append(groupValues, slog.Any("msg", err.Error()))
}
type StackTracer interface {
StackTrace() errors.StackTrace
}
var st StackTracer
for err := err; err != nil; err = errors.Unwrap(err) {
if x, ok := err.(StackTracer); ok {
st = x
}
}
if st == nil {
st = errors.WithStack(err).(StackTracer)
}
if st != nil {
if beauty {
groupValues = append(groupValues,
slog.Any("trace", &beautyTrace{traceLines(st.StackTrace())}),
)
} else {
groupValues = append(groupValues,
slog.Any("trace", traceLines(st.StackTrace())),
)
}
}
return slog.GroupValue(groupValues...)
}
func traceLines(frames errors.StackTrace) []string {
traceLines := make([]string, len(frames))
var skipped int
skipping := true
for i := len(frames) - 1; i >= 0; i-- {
pc := uintptr(frames[i]) - 1
fn := runtime.FuncForPC(pc)
if fn == nil {
traceLines[i] = "unknown"
skipping = false
continue
}
name := fn.Name()
if skipping && strings.HasPrefix(name, "runtime.") {
skipped++
continue
} else {
skipping = false
}
filename, lineNr := fn.FileLine(pc)
traceLines[i] = fmt.Sprintf("%s %s:%d", name, filename, lineNr)
}
return traceLines[:len(traceLines)-skipped]
}