diff --git a/go.mod b/go.mod index 1d09c2d..d9d380b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 47cfead..2fecced 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/server/internal/v2/conn.go b/server/internal/v2/conn.go index d03382d..c2a038a 100644 --- a/server/internal/v2/conn.go +++ b/server/internal/v2/conn.go @@ -4,6 +4,7 @@ import ( "net" ) +// ConnWriter 用于兼容不同 Network 的连接数据写入器 type ConnWriter func(packet Packet) error type Conn interface { diff --git a/server/internal/v2/conn_context.go b/server/internal/v2/conn_context.go deleted file mode 100644 index 16b7d0f..0000000 --- a/server/internal/v2/conn_context.go +++ /dev/null @@ -1,4 +0,0 @@ -package server - -type ConnContext interface { -} diff --git a/server/internal/v2/events.go b/server/internal/v2/events.go index 338ea7e..7d5ea8d 100644 --- a/server/internal/v2/events.go +++ b/server/internal/v2/events.go @@ -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) diff --git a/server/internal/v2/network.go b/server/internal/v2/network.go index 053563d..140983b 100644 --- a/server/internal/v2/network.go +++ b/server/internal/v2/network.go @@ -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 } diff --git a/server/internal/v2/network/http.go b/server/internal/v2/network/http.go index 00273ee..0832ae4 100644 --- a/server/internal/v2/network/http.go +++ b/server/internal/v2/network/http.go @@ -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 +} diff --git a/server/internal/v2/network/websocket.go b/server/internal/v2/network/websocket.go index b75109a..a9eadb5 100644 --- a/server/internal/v2/network/websocket.go +++ b/server/internal/v2/network/websocket.go @@ -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) +} diff --git a/server/internal/v2/options.go b/server/internal/v2/options.go index 1fefbc3..3d9e9e0 100644 --- a/server/internal/v2/options.go +++ b/server/internal/v2/options.go @@ -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() diff --git a/server/internal/v2/pprof.go b/server/internal/v2/pprof.go index 6379157..bc4e9ea 100644 --- a/server/internal/v2/pprof.go +++ b/server/internal/v2/pprof.go @@ -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() diff --git a/server/internal/v2/reactor/reactor.go b/server/internal/v2/reactor/reactor.go index b889e8e..3b23e6d 100644 --- a/server/internal/v2/reactor/reactor.go +++ b/server/internal/v2/reactor/reactor.go @@ -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 使用的队列 diff --git a/server/internal/v2/server.go b/server/internal/v2/server.go index 864fa7a..9093dab 100644 --- a/server/internal/v2/server.go +++ b/server/internal/v2/server.go @@ -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() diff --git a/server/internal/v2/server_test.go b/server/internal/v2/server_test.go index c959829..f48f253 100644 --- a/server/internal/v2/server_test.go +++ b/server/internal/v2/server_test.go @@ -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) diff --git a/utils/log/v2/color.go b/utils/log/v2/color.go new file mode 100644 index 0000000..bff8165 --- /dev/null +++ b/utils/log/v2/color.go @@ -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 +} diff --git a/utils/log/v2/defines.go b/utils/log/v2/defines.go new file mode 100644 index 0000000..445a863 --- /dev/null +++ b/utils/log/v2/defines.go @@ -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 +} diff --git a/utils/log/v2/field.go b/utils/log/v2/field.go new file mode 100644 index 0000000..03c7c24 --- /dev/null +++ b/utils/log/v2/field.go @@ -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) +} diff --git a/utils/log/v2/handler.go b/utils/log/v2/handler.go new file mode 100644 index 0000000..c6b98d5 --- /dev/null +++ b/utils/log/v2/handler.go @@ -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(' ') +} diff --git a/utils/log/v2/logger.go b/utils/log/v2/logger.go new file mode 100644 index 0000000..0061120 --- /dev/null +++ b/utils/log/v2/logger.go @@ -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 +} diff --git a/utils/log/v2/logger_test.go b/utils/log/v2/logger_test.go new file mode 100644 index 0000000..fe36214 --- /dev/null +++ b/utils/log/v2/logger_test.go @@ -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...) + } + +} diff --git a/utils/log/v2/multi_handler.go b/utils/log/v2/multi_handler.go new file mode 100644 index 0000000..6533cec --- /dev/null +++ b/utils/log/v2/multi_handler.go @@ -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...) +} diff --git a/utils/log/v2/options.go b/utils/log/v2/options.go new file mode 100644 index 0000000..f75f45c --- /dev/null +++ b/utils/log/v2/options.go @@ -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) +} diff --git a/utils/log/v2/stack_trace.go b/utils/log/v2/stack_trace.go new file mode 100644 index 0000000..890abd7 --- /dev/null +++ b/utils/log/v2/stack_trace.go @@ -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] +}