refactor: log 包重构,优化使用方式

This commit is contained in:
kercylan98 2023-07-12 18:34:45 +08:00
parent 94147e8b9c
commit 98234e5f86
13 changed files with 637 additions and 190 deletions

View File

@ -1,6 +1,19 @@
package server
import "time"
import (
"github.com/kercylan98/minotaur/utils/log"
"time"
)
type (
RunMode = log.RunMode
)
const (
RunModeDev RunMode = log.RunModeDev
RunModeProd RunMode = log.RunModeProd
RunModeTest RunMode = log.RunModeTest
)
const (
serverMultipleMark = "Minotaur Multiple Server"

View File

@ -3,9 +3,7 @@ package server
import (
"encoding/json"
"fmt"
"github.com/kercylan98/minotaur/utils/str"
"reflect"
"runtime/debug"
)
const (
@ -69,11 +67,14 @@ func (slf *Message) String() string {
}
attrs = append(attrs, attr)
}
raw, _ := json.Marshal(attrs)
s := string(raw)
if s == str.None {
var s string
if len(slf.attrs) == 0 {
s = "NoneAttr"
} else {
raw, _ := json.Marshal(attrs)
s = string(raw)
}
return fmt.Sprintf("[%s] %s", slf.t, s)
}
@ -93,7 +94,7 @@ func PushPacketMessage(srv *Server, conn *Conn, packet []byte, mark ...any) {
func PushErrorMessage(srv *Server, err error, action MessageErrorAction, mark ...any) {
msg := srv.messagePool.Get()
msg.t = MessageTypeError
msg.attrs = append([]any{err, action, string(debug.Stack())}, mark...)
msg.attrs = append([]any{err, action}, mark...)
srv.pushMessage(msg)
}
@ -128,6 +129,6 @@ func PushTickerMessage(srv *Server, caller func(), mark ...any) {
func PushAsyncMessage(srv *Server, caller func() error, callback func(err error), mark ...any) {
msg := srv.messagePool.Get()
msg.t = MessageTypeAsync
msg.attrs = append([]any{caller, callback, string(debug.Stack())}, mark...)
msg.attrs = append([]any{caller, callback}, mark...)
srv.pushMessage(msg)
}

View File

@ -37,7 +37,6 @@ type runtime struct {
certFile, keyFile string // TLS文件
messagePoolSize int // 消息池大小
messageChannelSize int // 消息通道大小
prod bool // 是否为生产模式
ticker *timer.Ticker // 定时器
websocketReadDeadline time.Duration // websocket连接超时时间
websocketCompression int // websocket压缩等级
@ -184,10 +183,11 @@ func WithGRPCServerOptions(options ...grpc.ServerOption) Option {
}
}
// WithProd 通过生产模式运行服务器
func WithProd() Option {
// WithRunMode 通过特定模式运行服务器
// - 默认为 RunModeDev
func WithRunMode(mode RunMode) Option {
return func(srv *Server) {
srv.prod = true
srv.runMode = mode
}
}

View File

@ -60,7 +60,7 @@ func New(network Network, options ...Option) *Server {
server.antsPoolSize = DefaultAsyncPoolSize
}
var err error
server.ants, err = ants.NewPool(server.antsPoolSize, ants.WithLogger(log.Logger()))
server.ants, err = ants.NewPool(server.antsPoolSize, ants.WithLogger(log.GetLogger()))
if err != nil {
panic(err)
}
@ -91,6 +91,7 @@ type Server struct {
messageChannel chan *Message // 消息管道
multiple *MultipleServer // 多服务器模式下的服务器
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
runMode RunMode // 运行模式
}
// Run 使用特定地址运行服务器
@ -156,8 +157,8 @@ func (slf *Server) Run(addr string) error {
slf.isRunning = true
slf.OnStartBeforeEvent()
if err := gnet.Serve(slf.gServer, protoAddr,
gnet.WithLogger(log.Logger().Sugar()),
gnet.WithLogLevel(super.If(slf.IsProd(), logging.ErrorLevel, logging.DebugLevel)),
gnet.WithLogger(log.GetLogger()),
gnet.WithLogLevel(super.If(slf.runMode == RunModeProd, logging.ErrorLevel, logging.DebugLevel)),
gnet.WithTicker(true),
gnet.WithMulticore(true),
); err != nil {
@ -201,8 +202,12 @@ func (slf *Server) Run(addr string) error {
}
})
case NetworkHttp:
if slf.prod {
log.SetProd(slf.prod)
switch slf.runMode {
case RunModeDev:
gin.SetMode(gin.DebugMode)
case RunModeTest:
gin.SetMode(gin.TestMode)
case RunModeProd:
gin.SetMode(gin.ReleaseMode)
}
go func() {
@ -359,16 +364,6 @@ func (slf *Server) CloseConn(id string) {
}
}
// IsProd 是否为生产模式
func (slf *Server) IsProd() bool {
return slf.prod
}
// IsDev 是否为开发模式
func (slf *Server) IsDev() bool {
return !slf.prod
}
// GetID 获取服务器id
func (slf *Server) GetID() int64 {
if slf.cross == nil {
@ -391,7 +386,7 @@ func (slf *Server) Shutdown() {
}
// shutdown 停止运行服务器
func (slf *Server) shutdown(err error, stack ...string) {
func (slf *Server) shutdown(err error) {
slf.OnStopEvent()
defer func() {
if slf.multipleRuntimeErrorChan != nil {
@ -426,13 +421,9 @@ func (slf *Server) shutdown(err error, stack ...string) {
}
if err != nil {
var s string
if len(stack) > 0 {
s = stack[0]
}
if slf.multiple != nil {
slf.multiple.RegExitEvent(func() {
log.ErrorWithStack("Server", s, zap.Any("network", slf.network), zap.String("listen", slf.addr),
log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr),
zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err))
})
for i, server := range slf.multiple.servers {
@ -442,7 +433,7 @@ func (slf *Server) shutdown(err error, stack ...string) {
}
}
} else {
log.ErrorWithStack("Server", s, zap.Any("network", slf.network), zap.String("listen", slf.addr),
log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr),
zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err))
}
} else {
@ -482,7 +473,7 @@ func (slf *Server) pushMessage(message *Message) {
func (slf *Server) low(message *Message, present time.Time, expect time.Duration) {
cost := time.Since(present)
if cost > expect {
log.Warn("Server", zap.String("LowExecCost", cost.String()), zap.Any("Message", message))
log.Warn("Server", zap.String("type", "low-message"), zap.String("cost", cost.String()), zap.String("message", message.String()), zap.Stack("stack"))
slf.OnMessageLowExecEvent(message, cost)
}
}
@ -508,19 +499,22 @@ func (slf *Server) dispatchMessage(msg *Message) {
present := time.Now()
defer func() {
if err := recover(); err != nil {
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err))
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err), zap.Stack("stack"))
if e, ok := err.(error); ok {
slf.OnMessageErrorEvent(msg, e)
}
}
if msg.t != MessageTypeAsync {
if msg.t == MessageTypeAsync {
return
}
super.Handle(cancel)
slf.low(msg, present, time.Millisecond*100)
if !slf.isShutdown.Load() {
slf.messagePool.Release(msg)
}
}
}()
var attrs = msg.attrs
@ -530,12 +524,12 @@ func (slf *Server) dispatchMessage(msg *Message) {
var wst = int(packet[len(packet)-1])
slf.OnConnectionReceivePacketEvent(attrs[0].(*Conn), Packet{Data: packet[:len(packet)-1], WebsocketType: wst})
case MessageTypeError:
err, action, stack := attrs[0].(error), attrs[1].(MessageErrorAction), attrs[2].(string)
err, action := attrs[0].(error), attrs[1].(MessageErrorAction)
switch action {
case MessageErrorActionNone:
log.ErrorWithStack("Server", stack, zap.Error(err))
log.Panic("Server", zap.Error(err))
case MessageErrorActionShutdown:
slf.shutdown(err, stack)
slf.shutdown(err)
default:
log.Warn("Server", zap.String("not support message error action", action.String()))
}
@ -549,13 +543,14 @@ func (slf *Server) dispatchMessage(msg *Message) {
if err := slf.ants.Submit(func() {
defer func() {
if err := recover(); err != nil {
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err))
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("error", err), zap.Stack("stack"))
if e, ok := err.(error); ok {
slf.OnMessageErrorEvent(msg, e)
}
}
super.Handle(cancel)
slf.low(msg, present, time.Second)
if !slf.isShutdown.Load() {
slf.messagePool.Release(msg)
}

View File

@ -36,6 +36,15 @@ func ToMap[V any](slice []V) map[int]V {
return m
}
// ToIterator 将切片转换为 Iterator
func ToIterator[V comparable](slice []V) map[V]struct{} {
var m = make(map[V]struct{})
for _, v := range slice {
m[v] = struct{}{}
}
return m
}
// ToMapBool 将切片转换为 mapvalue作为Key
func ToMapBool[V comparable](slice []V) map[V]bool {
var m = make(map[V]bool)

5
utils/log/core.go Normal file
View File

@ -0,0 +1,5 @@
package log
import "go.uber.org/zap/zapcore"
type Core = zapcore.Core

26
utils/log/encoder.go Normal file
View File

@ -0,0 +1,26 @@
package log
import (
"go.uber.org/zap/zapcore"
"time"
)
type Encoder = zapcore.Encoder
// NewEncoder 创建一个 Minotaur 默认使用的编码器
func NewEncoder() Encoder {
return zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "ts",
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(time.DateTime))
},
CallerKey: "file",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
},
})
}

159
utils/log/field.go Normal file
View File

@ -0,0 +1,159 @@
package log
import (
"go.uber.org/zap"
)
type Field = zap.Field
var (
// Skip 构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用
Skip = zap.Skip
// Binary 构造一个携带不透明二进制 blob 的字段。二进制数据以适合编码的格式进行序列化。例如JSON 编码器对二进制 blob 进行 base64 编码。要记录 UTF-8 编码文本,请使用 ByteString
Binary = zap.Binary
// Bool 构造一个带有布尔值的字段
Bool = zap.Bool
// BoolP 构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
BoolP = zap.Boolp
// ByteString 构造一个将 UTF-8 编码文本作为 [] 字节传送的字段。要记录不透明的二进制 blob不一定是有效的 UTF-8请使用 Binary
ByteString = zap.ByteString
// Complex128 构造一个带有复数的字段。与大多数数字字段不同这需要分配将complex128转换为interface{}
Complex128 = zap.Complex128
// Complex128P 构造一个带有complex128 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Complex128P = zap.Complex128p
// Complex64 构造一个带有复数的字段。与大多数数字字段不同这需要分配将complex64转换为interface{}
Complex64 = zap.Complex64
// Complex64P 构造一个带有complex64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Complex64P = zap.Complex64p
// Float64 构造一个带有 float64 的字段。浮点值的表示方式取决于编码器,因此封送处理必然是惰性的
Float64 = zap.Float64
// Float64P 构造一个带有 float64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Float64P = zap.Float64p
// Float32 构造一个带有 float32 的字段。浮点值的表示方式取决于编码器,因此封送处理必然是惰性的
Float32 = zap.Float32
// Float32P 构造一个带有 float32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Float32P = zap.Float32p
// Int constructs a field with the given key and value.
Int = zap.Int
// IntP 构造一个带有 int 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
IntP = zap.Intp
// Int64 使用给定的键和值构造一个字段.
Int64 = zap.Int64
// Int64P 构造一个带有 int64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int64P = zap.Int64p
// Int32 使用给定的键和值构造一个字段
Int32 = zap.Int32
// Int32P 构造一个带有 int32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”.
Int32P = zap.Int32p
// Int16 使用给定的键和值构造一个字段
Int16 = zap.Int16
// Int16P 构造一个带有 int16 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int16P = zap.Int16p
// Int8 使用给定的键和值构造一个字段
Int8 = zap.Int8
// Int8P 构造一个带有 int8 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int8P = zap.Int8p
// String 使用给定的键和值构造一个字段
String = zap.String
// StringP 构造一个带有字符串的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
StringP = zap.Stringp
// Uint 使用给定的键和值构造一个字段
Uint = zap.Uint
// UintP 构造一个带有 uint 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
UintP = zap.Uintp
// Uint64 使用给定的键和值构造一个字段
Uint64 = zap.Uint64
// Uint64P 构造一个带有 uint64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint64P = zap.Uint64p
// Uint32 使用给定的键和值构造一个字段
Uint32 = zap.Uint32
// Uint32P 构造一个带有 uint32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint32P = zap.Uint32p
// Uint16 使用给定的键和值构造一个字段
Uint16 = zap.Uint16
// Uint16P 构造一个带有 uint16 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint16P = zap.Uint16p
// Uint8 使用给定的键和值构造一个字段
Uint8 = zap.Uint8
// Uint8P 构造一个带有 uint8 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint8P = zap.Uint8p
// Uintptr 使用给定的键和值构造一个字段
Uintptr = zap.Uintptr
// UintptrP 构造一个带有 uintptr 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
UintptrP = zap.Uintptrp
// Reflect 使用给定的键和任意对象构造一个字段。它使用适当的编码、基于反射的方式将几乎任何对象延迟序列化到日志记录上下文中但它相对较慢且分配繁重。在测试之外Any 始终是更好的选择
// - 如果编码失败(例如,尝试将 map[int]string 序列化为 JSONReflect 将在最终日志输出中包含错误消息
Reflect = zap.Reflect
// Namespace 命名空间在记录器的上下文中创建一个命名的、隔离的范围。所有后续字段都将添加到新的命名空间中
// - 这有助于防止将记录器注入子组件或第三方库时发生按键冲突
Namespace = zap.Namespace
// Stringer 使用给定的键和值的 String 方法的输出构造一个字段。 Stringer 的 String 方法被延迟调用
Stringer = zap.Stringer
// Time 使用给定的键和值构造一个 Field。编码器控制时间的序列化方式
Time = zap.Time
// TimeP 构造一个带有 time.Time 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
TimeP = zap.Timep
// Stack 构造一个字段,在提供的键下存储当前 goroutine 的堆栈跟踪。请记住,进行堆栈跟踪是急切且昂贵的(相对而言);此操作会进行分配并花费大约两微秒的时间
Stack = zap.Stack
// StackSkip 构造一个与 Stack 类似的字段,但也会从堆栈跟踪顶部跳过给定数量的帧
StackSkip = zap.StackSkip
// Duration 使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式
Duration = zap.Duration
// DurationP 构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
DurationP = zap.Durationp
// Object 使用给定的键和 ObjectMarshaler 构造一个字段。它提供了一种灵活但仍然类型安全且高效的方法来将类似映射或结构的用户定义类型添加到日志记录上下文。该结构的 MarshalLogObject 方法被延迟调用
Object = zap.Object
// Inline 构造一个与 Object 类似的 Field但它会将提供的 ObjectMarshaler 的元素添加到当前命名空间
Inline = zap.Inline
// Any 接受一个键和一个任意值,并选择将它们表示为字段的最佳方式,仅在必要时才回退到基于反射的方法。
// 由于 byteuint8 和 runeint32 是别名Any 无法区分它们。为了尽量减少意外情况,[]byte 值被视为二进制 blob字节值被视为 uint8而 runes 始终被视为整数
Any = zap.Any
)

110
utils/log/level.go Normal file
View File

@ -0,0 +1,110 @@
package log
import (
"github.com/kercylan98/minotaur/utils/hash"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type Level = zapcore.Level
type LevelEnablerFunc = zap.LevelEnablerFunc
const (
// DebugLevel 调试级别日志通常非常庞大,并且通常在生产中被禁用
DebugLevel Level = zapcore.DebugLevel
// InfoLevel 是默认的日志记录优先级
InfoLevel Level = zapcore.InfoLevel
// WarnLevel 日志比信息更重要,但不需要单独的人工审核
WarnLevel Level = zapcore.WarnLevel
// ErrorLevel 日志具有高优先级。如果应用程序运行顺利,它不应该生成任何错误级别的日志
ErrorLevel Level = zapcore.ErrorLevel
// DPanicLevel 日志是特别重要的错误。在开发中,记录器在写入消息后会出现恐慌
DPanicLevel Level = zapcore.DPanicLevel
// PanicLevel 记录一条消息,然后出现恐慌
PanicLevel Level = zapcore.PanicLevel
// FatalLevel 记录一条消息,然后调用 os.Exit(1)
FatalLevel Level = zapcore.FatalLevel
)
var (
levels = []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel}
defaultLevelPartition = map[Level]func() LevelEnablerFunc{
DebugLevel: DebugLevelPartition,
InfoLevel: InfoLevelPartition,
WarnLevel: WarnLevelPartition,
ErrorLevel: ErrorLevelPartition,
DPanicLevel: DPanicLevelPartition,
PanicLevel: PanicLevelPartition,
FatalLevel: FatalLevelPartition,
}
)
// Levels 返回所有日志级别
func Levels() []Level {
return levels
}
// MultiLevelPartition 返回一个 LevelEnablerFunc该函数在指定的多个级别时返回 true
// - 该函数被用于划分不同级别的日志输出
func MultiLevelPartition(levels ...Level) LevelEnablerFunc {
var levelMap = hash.ToIterator(levels)
return func(level zapcore.Level) bool {
return hash.Exist(levelMap, level)
}
}
// DebugLevelPartition 返回一个 LevelEnablerFunc该函数在 DebugLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func DebugLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == DebugLevel
}
}
// InfoLevelPartition 返回一个 LevelEnablerFunc该函数在 InfoLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func InfoLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == InfoLevel
}
}
// WarnLevelPartition 返回一个 LevelEnablerFunc该函数在 WarnLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func WarnLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == WarnLevel
}
}
// ErrorLevelPartition 返回一个 LevelEnablerFunc该函数在 ErrorLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func ErrorLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == ErrorLevel
}
}
// DPanicLevelPartition 返回一个 LevelEnablerFunc该函数在 DPanicLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func DPanicLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == DPanicLevel
}
}
// PanicLevelPartition 返回一个 LevelEnablerFunc该函数在 PanicLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func PanicLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == PanicLevel
}
}
// FatalLevelPartition 返回一个 LevelEnablerFunc该函数在 FatalLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func FatalLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == FatalLevel
}
}

View File

@ -2,187 +2,169 @@ package log
import (
"fmt"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/kercylan98/minotaur/utils/str"
"github.com/kercylan98/minotaur/utils/times"
rotateLogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"runtime/debug"
"time"
"path/filepath"
"strings"
)
var (
logger *zap.Logger
prod bool
logPath string
logDevWrite bool
logTime = 7
)
func init() {
logger = newLogger()
if prod && len(logPath) == 0 {
Warn("Logger", zap.String("Tip", "in production mode, if the log file output directory is not set, only the console will be output"))
}
}
func newLogger() *zap.Logger {
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "ts",
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(time.DateTime))
// NewLog 创建一个日志记录器
func NewLog(options ...Option) *Log {
log := &Log{
filename: func(level Level) string {
return fmt.Sprintf("%s.log", level.String())
},
CallerKey: "file",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
rotateFilename: func(level Level) string {
return strings.Join([]string{level.String(), "%Y%m%d.log"}, ".")
},
})
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.InfoLevel
})
debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl <= zapcore.FatalLevel
})
errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
var cores zapcore.Core
if !prod {
if len(logPath) > 0 && logDevWrite {
infoWriter := getWriter(fmt.Sprintf("%s/info.log", logPath), logTime)
errorWriter := getWriter(fmt.Sprintf("%s/error.log", logPath), logTime)
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
} else {
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
levelPartition: defaultLevelPartition,
}
} else {
if len(logPath) == 0 {
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
} else {
infoWriter := getWriter(fmt.Sprintf("%s/info.log", logPath), logTime)
errorWriter := getWriter(fmt.Sprintf("%s/error.log", logPath), logTime)
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), errorLevel),
)
for _, option := range options {
option(log)
}
if len(log.rotateOptions) == 0 {
log.rotateOptions = []rotateLogs.Option{
rotateLogs.WithMaxAge(times.Week),
rotateLogs.WithRotationTime(times.Day),
}
}
return zap.New(cores, zap.AddCaller(), zap.AddCallerSkip(1))
}
if len(log.cores) == 0 {
var encoder = NewEncoder()
func getWriter(filename string, times int) io.Writer {
hook, err := rotatelogs.New(
filename+".%Y%m%d",
rotatelogs.WithLinkName(filename),
rotatelogs.WithMaxAge(time.Hour*24*7),
rotatelogs.WithRotationTime(time.Hour*time.Duration(times)),
switch log.mode {
case RunModeDev:
var partition LevelEnablerFunc = func(lvl Level) bool {
return true
}
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, partition))
case RunModeTest, RunModeProd:
if log.mode == RunModeTest {
infoRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(InfoLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(InfoLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
return hook
}
type MLogger struct {
*zap.Logger
}
func (slf *MLogger) Printf(format string, args ...interface{}) {
slf.Info(fmt.Sprintf(format, args...))
}
func Logger() *MLogger {
return &MLogger{logger}
}
func Info(msg string, fields ...zap.Field) {
logger.Info(msg, fields...)
}
func Warn(msg string, fields ...zap.Field) {
logger.Warn(msg, fields...)
}
func Debug(msg string, fields ...zap.Field) {
logger.Debug(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
logger.Error(msg, fields...)
fmt.Println(string(debug.Stack()))
}
func ErrorHideStack(msg string, fields ...zap.Field) {
logger.Error(msg, fields...)
}
// ErrorWithStack 通过额外的堆栈信息打印错误日志
func ErrorWithStack(msg, stack string, fields ...zap.Field) {
logger.Error(msg, fields...)
var stackMerge string
if len(stack) > 0 {
stackMerge = stack
errRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(ErrorLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(ErrorLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
stackMerge += string(debug.Stack())
fmt.Println(stackMerge)
if log.logDir != str.None {
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(infoRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl < ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(errRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, LevelEnablerFunc(func(lvl Level) bool { return lvl < ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
}
} else {
infoRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(InfoLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(InfoLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
errRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(ErrorLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(ErrorLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
if log.logDir != str.None {
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(infoRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl == InfoLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(errRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
}
}
}
}
log.zap = zap.New(zapcore.NewTee(log.cores...), zap.AddCaller(), zap.AddCallerSkip(1))
log.sugar = log.zap.Sugar()
return log
}
// SetProd 设置生产环境模式
func SetProd(isProd bool) {
if prod == isProd {
return
}
prod = isProd
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
type Log struct {
zap *zap.Logger
sugar *zap.SugaredLogger
filename func(level Level) string
rotateFilename func(level Level) string
rotateOptions []rotateLogs.Option
levelPartition map[Level]func() LevelEnablerFunc
cores []Core
mode RunMode
logDir string
rotateLogDir string
}
// SetLogDir 设置日志输出目录
func SetLogDir(dir string) {
logPath = dir
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
func (slf *Log) Debugf(format string, args ...interface{}) {
slf.sugar.Debugf(format, args...)
}
// SetWriteFileWithDev 设置开发环境下写入文件
func SetWriteFileWithDev(isWrite bool) {
if isWrite == logDevWrite {
return
}
logDevWrite = isWrite
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
func (slf *Log) Infof(format string, args ...interface{}) {
slf.sugar.Infof(format, args...)
}
// SetLogRotate 设置日志切割时间
func SetLogRotate(t int) {
logTime = t
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
func (slf *Log) Warnf(format string, args ...interface{}) {
slf.sugar.Warnf(format, args...)
}
func (slf *Log) Errorf(format string, args ...interface{}) {
slf.sugar.Errorf(format, args...)
}
func (slf *Log) Fatalf(format string, args ...interface{}) {
slf.sugar.Fatalf(format, args...)
}
func (slf *Log) Printf(format string, args ...interface{}) {
slf.sugar.Infof(format, args...)
}
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Debug(msg string, fields ...Field) {
slf.zap.Debug(msg, fields...)
}
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Info(msg string, fields ...Field) {
slf.zap.Info(msg, fields...)
}
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Warn(msg string, fields ...Field) {
slf.zap.Warn(msg, fields...)
}
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Error(msg string, fields ...Field) {
slf.zap.Error(msg, fields...)
}
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
func (slf *Log) DPanic(msg string, fields ...Field) {
slf.zap.DPanic(msg, fields...)
}
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
func (slf *Log) Panic(msg string, fields ...Field) {
slf.zap.Panic(msg, fields...)
}
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
func (slf *Log) Fatal(msg string, fields ...Field) {
slf.zap.Fatal(msg, fields...)
}

79
utils/log/logger.go Normal file
View File

@ -0,0 +1,79 @@
package log
import (
"github.com/panjf2000/ants/v2"
"github.com/panjf2000/gnet/pkg/logging"
)
var logger Logger = NewLog()
// Logger 适用于 Minotaur 的日志接口
type Logger interface {
ants.Logger
logging.Logger
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Debug(msg string, fields ...Field)
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Info(msg string, fields ...Field)
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Warn(msg string, fields ...Field)
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Error(msg string, fields ...Field)
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
DPanic(msg string, fields ...Field)
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
Panic(msg string, fields ...Field)
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
Fatal(msg string, fields ...Field)
}
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Debug(msg string, fields ...Field) {
logger.Debug(msg, fields...)
}
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Info(msg string, fields ...Field) {
logger.Info(msg, fields...)
}
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Warn(msg string, fields ...Field) {
logger.Warn(msg, fields...)
}
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Error(msg string, fields ...Field) {
logger.Error(msg, fields...)
}
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
func DPanic(msg string, fields ...Field) {
logger.DPanic(msg, fields...)
}
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
func Panic(msg string, fields ...Field) {
logger.Panic(msg, fields...)
}
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
func Fatal(msg string, fields ...Field) {
logger.Fatal(msg, fields...)
}
// SetLogger 设置日志记录器
func SetLogger(log Logger) {
logger = log
}
// GetLogger 获取日志记录器
func GetLogger() Logger {
return logger
}

52
utils/log/options.go Normal file
View File

@ -0,0 +1,52 @@
package log
import (
rotateLogs "github.com/lestrrat-go/file-rotatelogs"
)
type Option func(log *Log)
// WithRunMode 设置运行模式
// - 默认的运行模式为: RunModeDev
// - 当 handle 不为空时,将会调用 handle(),并将返回值添加到日志记录器中,同时将会抑制默认的日志记录器
func WithRunMode(mode RunMode, handle func() Core) Option {
return func(log *Log) {
log.mode = mode
if handle != nil {
log.cores = append(log.cores, handle())
}
}
}
// WithFilename 设置日志文件名
// - 默认的日志文件名为: {level}.log
func WithFilename(filename func(level Level) string) Option {
return func(log *Log) {
log.filename = filename
}
}
// WithRotateFilename 设置日志分割文件名
// - 默认的日志分割文件名为: {level}.%Y%m%d.log
func WithRotateFilename(filename func(level Level) string) Option {
return func(log *Log) {
log.rotateFilename = filename
}
}
// WithRotateOption 设置日志分割选项
// - 默认的日志分割选项为: WithMaxAge(7天), WithRotationTime(1天)
func WithRotateOption(options ...rotateLogs.Option) Option {
return func(log *Log) {
log.rotateOptions = options
}
}
// WithLogDir 设置日志文件夹
// - 默认情况下不会设置日志文件夹,日志将不会被文件存储
func WithLogDir(logDir, rotateLogDir string) Option {
return func(log *Log) {
log.logDir = logDir
log.rotateLogDir = rotateLogDir
}
}

16
utils/log/run_mode.go Normal file
View File

@ -0,0 +1,16 @@
package log
const (
// RunModeDev 开发模式是默认的运行模式,同时也是最基础的运行模式
// - 开发模式下,将会输出所有级别的日志到控制台
// - 默认不再输出日志到文件
RunModeDev RunMode = iota
// RunModeTest 测试模式是一种特殊的运行模式,用于测试
// - 测试模式下,将会输出所有级别的日志到控制台和文件
RunModeTest
// RunModeProd 生产模式是一种特殊的运行模式,用于生产
// - 生产模式下,将会输出 InfoLevel 及以上级别的日志到控制台和文件
RunModeProd
)
type RunMode uint8