other: 新版 server、logger 完善
This commit is contained in:
parent
49b8efd9b2
commit
e4eee31ede
5
go.mod
5
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
|
||||
|
|
12
go.sum
12
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=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net"
|
||||
)
|
||||
|
||||
// ConnWriter 用于兼容不同 Network 的连接数据写入器
|
||||
type ConnWriter func(packet Packet) error
|
||||
|
||||
type Conn interface {
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package server
|
||||
|
||||
type ConnContext interface {
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 使用的队列
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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(' ')
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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...)
|
||||
}
|
||||
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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]
|
||||
}
|
Loading…
Reference in New Issue