other: 新版 server、logger 完善
This commit is contained in:
parent
49b8efd9b2
commit
e4eee31ede
5
go.mod
5
go.mod
|
@ -28,9 +28,11 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.0.0-20240315062850-21fc7a1671a8 // indirect
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // 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/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // 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/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // 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 h1:KmZh75duO1tC8pt3LmUwoTYiZ9sh4K52FX8p7/yrlqU=
|
||||||
github.com/alphadose/haxmap v1.3.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
|
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/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.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 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
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/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 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
|
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.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 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
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.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 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.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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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-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-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-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.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
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-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-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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.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=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConnWriter 用于兼容不同 Network 的连接数据写入器
|
||||||
type ConnWriter func(packet Packet) error
|
type ConnWriter func(packet Packet) error
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
type ConnContext interface {
|
|
||||||
}
|
|
|
@ -1,6 +1,9 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/log"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kercylan98/minotaur/utils/collection/listings"
|
"github.com/kercylan98/minotaur/utils/collection/listings"
|
||||||
|
@ -57,6 +60,12 @@ func (s *events) RegisterLaunchedEvent(handler LaunchedEventHandler, priority ..
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *events) onLaunched() {
|
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.server.reactor.SystemDispatch(NativeMessage(s.server, func(srv *server) {
|
||||||
s.launchedEventHandlers.RangeValue(func(index int, value LaunchedEventHandler) bool {
|
s.launchedEventHandlers.RangeValue(func(index int, value LaunchedEventHandler) bool {
|
||||||
value(s.server, s.server.state.Ip, s.server.state.LaunchedAt)
|
value(s.server, s.server.state.Ip, s.server.state.LaunchedAt)
|
||||||
|
|
|
@ -4,10 +4,20 @@ import (
|
||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Network 提供给 Server 使用的网络接口
|
||||||
type Network interface {
|
type Network interface {
|
||||||
|
// OnSetup 装载函数,该函数将传入 Server 的上下文及一个可用于控制 Server 行为的控制器,在该函数中应该完成 Network 实现的初始化工作
|
||||||
OnSetup(ctx context.Context, controller Controller) error
|
OnSetup(ctx context.Context, controller Controller) error
|
||||||
|
|
||||||
|
// OnRun 运行函数,在该函数中通常是进行网络监听启动的阻塞行为
|
||||||
OnRun() error
|
OnRun() error
|
||||||
|
|
||||||
|
// OnShutdown 停止函数,该函数在执行的时候应该完成对资源的停止及释放
|
||||||
OnShutdown() error
|
OnShutdown() error
|
||||||
|
|
||||||
|
// Schema 获取协议标识
|
||||||
|
Schema() string
|
||||||
|
|
||||||
|
// Address 获取监听地址信息
|
||||||
|
Address() string
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,3 +53,11 @@ func (h *httpCore[H]) OnShutdown() error {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return h.srv.Shutdown(ctx)
|
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
|
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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/log/v2"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -11,24 +12,27 @@ func NewOptions() *Options {
|
||||||
|
|
||||||
func DefaultOptions() *Options {
|
func DefaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
ServerMessageChannelSize: 1024 * 4,
|
serverMessageChannelSize: 1024 * 4,
|
||||||
ActorMessageChannelSize: 1024,
|
actorMessageChannelSize: 1024,
|
||||||
ServerMessageBufferInitialSize: 1024,
|
serverMessageBufferInitialSize: 1024,
|
||||||
ActorMessageBufferInitialSize: 1024,
|
actorMessageBufferInitialSize: 1024,
|
||||||
MessageErrorHandler: nil,
|
messageErrorHandler: nil,
|
||||||
LifeCycleLimit: 0,
|
lifeCycleLimit: 0,
|
||||||
|
logger: log.GetLogger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
server *server
|
server *server
|
||||||
rw sync.RWMutex
|
rw sync.RWMutex
|
||||||
ServerMessageChannelSize int // 服务器 Actor 消息处理管道大小
|
serverMessageChannelSize int // 服务器 Actor 消息处理管道大小
|
||||||
ActorMessageChannelSize int // Actor 消息处理管道大小
|
actorMessageChannelSize int // Actor 消息处理管道大小
|
||||||
ServerMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
|
serverMessageBufferInitialSize int // 服务器 Actor 消息写入缓冲区初始化大小
|
||||||
ActorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
|
actorMessageBufferInitialSize int // Actor 消息写入缓冲区初始化大小
|
||||||
MessageErrorHandler func(srv Server, message Message, err error) // 消息错误处理器
|
messageErrorHandler func(srv Server, message Message, err error) // 消息错误处理器
|
||||||
LifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
|
lifeCycleLimit time.Duration // 服务器生命周期上限,在服务器启动后达到生命周期上限将关闭服务器
|
||||||
|
logger *log.Logger // 日志记录器
|
||||||
|
debug bool // Debug 模式
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Options) init(srv *server) *Options {
|
func (opt *Options) init(srv *server) *Options {
|
||||||
|
@ -42,12 +46,13 @@ func (opt *Options) Apply(options ...*Options) {
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option.rw.RLock()
|
option.rw.RLock()
|
||||||
|
|
||||||
opt.ServerMessageChannelSize = option.ServerMessageChannelSize
|
opt.serverMessageChannelSize = option.serverMessageChannelSize
|
||||||
opt.ActorMessageChannelSize = option.ActorMessageChannelSize
|
opt.actorMessageChannelSize = option.actorMessageChannelSize
|
||||||
opt.ServerMessageBufferInitialSize = option.ServerMessageBufferInitialSize
|
opt.serverMessageBufferInitialSize = option.serverMessageBufferInitialSize
|
||||||
opt.ActorMessageBufferInitialSize = option.ActorMessageBufferInitialSize
|
opt.actorMessageBufferInitialSize = option.actorMessageBufferInitialSize
|
||||||
opt.MessageErrorHandler = option.MessageErrorHandler
|
opt.messageErrorHandler = option.messageErrorHandler
|
||||||
opt.LifeCycleLimit = option.LifeCycleLimit
|
opt.lifeCycleLimit = option.lifeCycleLimit
|
||||||
|
opt.logger = option.logger
|
||||||
|
|
||||||
option.rw.RUnlock()
|
option.rw.RUnlock()
|
||||||
}
|
}
|
||||||
|
@ -57,32 +62,62 @@ func (opt *Options) Apply(options ...*Options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Options) active() {
|
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 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
|
// WithServerMessageChannelSize 设置服务器 Actor 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
|
||||||
func (opt *Options) WithServerMessageChannelSize(size int) *Options {
|
func (opt *Options) WithServerMessageChannelSize(size int) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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 getOptionsValue(opt, func(opt *Options) int {
|
||||||
return opt.ServerMessageChannelSize
|
return opt.serverMessageChannelSize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithActorMessageChannelSize 设置 Actor 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
|
// WithActorMessageChannelSize 设置 Actor 用于处理消息的管道大小,当管道由于逻辑阻塞而导致满载时,会导致新消息无法及时从缓冲区拿出,从而增加内存的消耗,但是并不会影响消息的写入
|
||||||
func (opt *Options) WithActorMessageChannelSize(size int) *Options {
|
func (opt *Options) WithActorMessageChannelSize(size int) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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 getOptionsValue(opt, func(opt *Options) int {
|
||||||
return opt.ActorMessageChannelSize
|
return opt.actorMessageChannelSize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,13 +125,13 @@ func (opt *Options) getActorMessageChannelSize() int {
|
||||||
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
|
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
|
||||||
func (opt *Options) WithServerMessageBufferInitialSize(size int) *Options {
|
func (opt *Options) WithServerMessageBufferInitialSize(size int) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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 getOptionsValue(opt, func(opt *Options) int {
|
||||||
return opt.ServerMessageBufferInitialSize
|
return opt.serverMessageBufferInitialSize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,13 +139,13 @@ func (opt *Options) getServerMessageBufferInitialSize() int {
|
||||||
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
|
// - 由于扩容是按照当前大小的 2 倍进行扩容,过大的值也可能会导致内存消耗过高
|
||||||
func (opt *Options) WithActorMessageBufferInitialSize(size int) *Options {
|
func (opt *Options) WithActorMessageBufferInitialSize(size int) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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 getOptionsValue(opt, func(opt *Options) int {
|
||||||
return opt.ActorMessageBufferInitialSize
|
return opt.actorMessageBufferInitialSize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,13 +153,13 @@ func (opt *Options) getActorMessageBufferInitialSize() int {
|
||||||
// - 如果在运行时设置,后续消息错误将会使用新的 handler 进行处理
|
// - 如果在运行时设置,后续消息错误将会使用新的 handler 进行处理
|
||||||
func (opt *Options) WithMessageErrorHandler(handler func(srv Server, message Message, err error)) *Options {
|
func (opt *Options) WithMessageErrorHandler(handler func(srv Server, message Message, err error)) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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 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 {
|
func (opt *Options) WithLifeCycleLimit(limit time.Duration) *Options {
|
||||||
return opt.modifyOptionsValue(func(opt *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) {
|
return opt.modifyOptionsValue(func(opt *Options) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if end.Before(now) {
|
if end.Before(now) {
|
||||||
opt.LifeCycleLimit = 0
|
opt.lifeCycleLimit = 0
|
||||||
return
|
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 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
|
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 {
|
func getOptionsValue[V any](opt *Options, handler func(opt *Options) V) V {
|
||||||
opt.rw.RLock()
|
opt.rw.RLock()
|
||||||
defer opt.rw.RUnlock()
|
defer opt.rw.RUnlock()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
|
@ -12,8 +13,8 @@ var (
|
||||||
httpPProfMutex sync.Mutex // HTTP PProf 服务器互斥锁
|
httpPProfMutex sync.Mutex // HTTP PProf 服务器互斥锁
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnableHttpPProf 设置启用 http pprof
|
// EnableHttpPProf 设置启用 HTTP PProf
|
||||||
// - 该函数支持运行时调用
|
// - 该函数支持运行时调用且支持重复调用,重复调用不会重复开启
|
||||||
func EnableHttpPProf(addr, prefix string, errorHandler func(err error)) {
|
func EnableHttpPProf(addr, prefix string, errorHandler func(err error)) {
|
||||||
httpPProfMutex.Lock()
|
httpPProfMutex.Lock()
|
||||||
defer httpPProfMutex.Unlock()
|
defer httpPProfMutex.Unlock()
|
||||||
|
@ -36,14 +37,15 @@ func EnableHttpPProf(addr, prefix string, errorHandler func(err error)) {
|
||||||
srv := &http.Server{Addr: addr, Handler: mux}
|
srv := &http.Server{Addr: addr, Handler: mux}
|
||||||
httpPProf = srv
|
httpPProf = srv
|
||||||
go func(srv *http.Server, errHandler func(err error)) {
|
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)
|
errorHandler(err)
|
||||||
}
|
}
|
||||||
}(srv, errorHandler)
|
}(srv, errorHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableHttpPProf 设置禁用 http pprof
|
// DisableHttpPProf 设置禁用 HTTP PProf
|
||||||
// - 该函数支持运行时调用
|
// - 当 HTTP PProf 未启用时不会产生任何效果
|
||||||
|
// - 该函数支持运行时调用且支持重复调用,重复调用不会重复禁用
|
||||||
func DisableHttpPProf() {
|
func DisableHttpPProf() {
|
||||||
httpPProfMutex.Lock()
|
httpPProfMutex.Lock()
|
||||||
defer httpPProfMutex.Unlock()
|
defer httpPProfMutex.Unlock()
|
||||||
|
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alphadose/haxmap"
|
"github.com/alphadose/haxmap"
|
||||||
"github.com/kercylan98/minotaur/server/internal/v2/loadbalancer"
|
"github.com/kercylan98/minotaur/server/internal/v2/loadbalancer"
|
||||||
"github.com/kercylan98/minotaur/utils/log"
|
|
||||||
"github.com/kercylan98/minotaur/utils/super"
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
"log/slog"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -25,7 +23,6 @@ var sysIdent = &identifiable{ident: "system"}
|
||||||
// NewReactor 创建一个新的 Reactor 实例,初始化系统级别的队列和多个 Socket 对应的队列
|
// NewReactor 创建一个新的 Reactor 实例,初始化系统级别的队列和多个 Socket 对应的队列
|
||||||
func NewReactor[M any](systemQueueSize, queueSize, systemBufferSize, queueBufferSize int, handler MessageHandler[M], errorHandler ErrorHandler[M]) *Reactor[M] {
|
func NewReactor[M any](systemQueueSize, queueSize, systemBufferSize, queueBufferSize int, handler MessageHandler[M], errorHandler ErrorHandler[M]) *Reactor[M] {
|
||||||
r := &Reactor[M]{
|
r := &Reactor[M]{
|
||||||
logger: log.Default().Logger,
|
|
||||||
systemQueue: newQueue[M](-1, systemQueueSize, systemBufferSize),
|
systemQueue: newQueue[M](-1, systemQueueSize, systemBufferSize),
|
||||||
identifiers: haxmap.New[string, *identifiable](),
|
identifiers: haxmap.New[string, *identifiable](),
|
||||||
lb: loadbalancer.NewRoundRobin[int, *queue[M]](),
|
lb: loadbalancer.NewRoundRobin[int, *queue[M]](),
|
||||||
|
@ -80,7 +77,6 @@ func NewReactor[M any](systemQueueSize, queueSize, systemBufferSize, queueBuffer
|
||||||
|
|
||||||
// Reactor 是一个消息反应器,管理系统级别的队列和多个 Socket 对应的队列
|
// Reactor 是一个消息反应器,管理系统级别的队列和多个 Socket 对应的队列
|
||||||
type Reactor[M any] struct {
|
type Reactor[M any] struct {
|
||||||
logger *slog.Logger // 日志记录器
|
|
||||||
state int32 // 状态
|
state int32 // 状态
|
||||||
systemQueue *queue[M] // 系统级别的队列
|
systemQueue *queue[M] // 系统级别的队列
|
||||||
queueSize int // 队列管道大小
|
queueSize int // 队列管道大小
|
||||||
|
@ -93,13 +89,6 @@ type Reactor[M any] struct {
|
||||||
cwg sync.WaitGroup // 关闭等待组
|
cwg sync.WaitGroup // 关闭等待组
|
||||||
handler queueMessageHandler[M] // 消息处理器
|
handler queueMessageHandler[M] // 消息处理器
|
||||||
errorHandler ErrorHandler[M] // 错误处理器
|
errorHandler ErrorHandler[M] // 错误处理器
|
||||||
debug bool // 是否开启调试模式
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDebug 设置是否开启调试模式
|
|
||||||
func (r *Reactor[M]) SetDebug(debug bool) *Reactor[M] {
|
|
||||||
r.debug = debug
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoDispatch 自动分发,当 ident 为空字符串时,分发到系统级别的队列,否则分发到 ident 使用的队列
|
// AutoDispatch 自动分发,当 ident 为空字符串时,分发到系统级别的队列,否则分发到 ident 使用的队列
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/kercylan98/minotaur/utils/log/v2"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kercylan98/minotaur/server/internal/v2/reactor"
|
"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.events = new(events).init(srv)
|
||||||
srv.state = new(State).init(srv)
|
srv.state = new(State).init(srv)
|
||||||
srv.reactor = reactor.NewReactor[Message](
|
srv.reactor = reactor.NewReactor[Message](
|
||||||
srv.getServerMessageChannelSize(), srv.getActorMessageChannelSize(),
|
srv.GetServerMessageChannelSize(), srv.GetActorMessageChannelSize(),
|
||||||
srv.getServerMessageBufferInitialSize(), srv.getActorMessageBufferInitialSize(),
|
srv.GetServerMessageBufferInitialSize(), srv.GetActorMessageBufferInitialSize(),
|
||||||
func(msg Message) {
|
func(msg Message) {
|
||||||
msg.Execute()
|
msg.Execute()
|
||||||
}, func(msg Message, err error) {
|
}, func(msg Message, err error) {
|
||||||
if handler := srv.getMessageErrorHandler(); handler != nil {
|
if handler := srv.GetMessageErrorHandler(); handler != nil {
|
||||||
handler(srv, msg, err)
|
handler(srv, msg, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -77,7 +78,13 @@ func (s *server) Run() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) Shutdown() (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()
|
defer s.cancel()
|
||||||
|
DisableHttpPProf()
|
||||||
s.events.onShutdown()
|
s.events.onShutdown()
|
||||||
err = s.network.OnShutdown()
|
err = s.network.OnShutdown()
|
||||||
s.reactor.Close()
|
s.reactor.Close()
|
||||||
|
|
|
@ -14,16 +14,17 @@ func TestNewServer(t *testing.T) {
|
||||||
panic(err)
|
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 := 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) {
|
srv.RegisterConnectionOpenedEvent(func(srv server.Server, conn server.Conn) {
|
||||||
if err := conn.WritePacket(server.NewPacket([]byte("hello")).SetContext(ws.OpText)); err != nil {
|
if err := conn.WritePacket(server.NewPacket([]byte("hello")).SetContext(ws.OpText)); err != nil {
|
||||||
t.Error(err)
|
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