From a23e48b087252995f8a42212bd77f3d0d8126578 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 11 Jul 2023 13:30:44 +0800 Subject: [PATCH 1/8] =?UTF-8?q?refactor:=20=E4=BB=BB=E5=8A=A1=20task=20?= =?UTF-8?q?=E5=8C=85=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/task/errors.go | 14 +++ game/task/events.go | 41 +++++---- game/task/options.go | 80 ++++++++++++++-- game/task/state.go | 8 +- game/task/task.go | 211 +++++++++++++++++++++++++++++++++++-------- utils/maths/math.go | 1 + 6 files changed, 289 insertions(+), 66 deletions(-) create mode 100644 game/task/errors.go diff --git a/game/task/errors.go b/game/task/errors.go new file mode 100644 index 0000000..a0c6230 --- /dev/null +++ b/game/task/errors.go @@ -0,0 +1,14 @@ +package task + +import "errors" + +var ( + // ErrTaskNotFinish 任务未完成 + ErrTaskNotFinish = errors.New("task not finish") + // ErrTaskRewardReceived 任务奖励已领取 + ErrTaskRewardReceived = errors.New("task reward received") + // ErrTaskNotStart 任务未开始 + ErrTaskNotStart = errors.New("task not start") + // ErrTaskFail 任务失败 + ErrTaskFail = errors.New("task fail") +) diff --git a/game/task/events.go b/game/task/events.go index 00dd3eb..c3925ff 100644 --- a/game/task/events.go +++ b/game/task/events.go @@ -1,32 +1,35 @@ package task type ( - TaskDoneEventHandle func(eventId int64, task *Task) + RefreshTaskCountEvent func(taskType int, increase int64) + RefreshTaskChildCountEvent func(taskType int, key any, increase int64) ) -var taskDoneEventHandles = make(map[int64]map[int64]TaskDoneEventHandle) +var ( + refreshTaskCountEventHandles = make(map[int]RefreshTaskCountEvent) + refreshTaskChildCountEventHandles = make(map[int]RefreshTaskChildCountEvent) +) -// RegTaskDoneEvent 注册任务完成事件 -func RegTaskDoneEvent(id int64, eventId int64, handle TaskDoneEventHandle) { - events, exist := taskDoneEventHandles[id] - if !exist { - events = map[int64]TaskDoneEventHandle{} - taskDoneEventHandles[id] = events - } - events[eventId] = handle +// RegRefreshTaskCount 注册任务计数刷新事件 +func RegRefreshTaskCount(taskType int, handler RefreshTaskCountEvent) { + refreshTaskCountEventHandles[taskType] = handler } -// UnRegTaskDoneEvent 取消注册任务完成事件 -func UnRegTaskDoneEvent(id int64, eventId int64) { - events, exist := taskDoneEventHandles[id] - if exist { - delete(events, eventId) +// OnRefreshTaskCount 触发任务计数刷新事件 +func OnRefreshTaskCount(taskType int, increase int64) { + if handler, ok := refreshTaskCountEventHandles[taskType]; ok { + handler(taskType, increase) } } -// OnTaskDoneEvent 任务完成事件 -func OnTaskDoneEvent(task *Task) { - for eventId, handle := range taskDoneEventHandles[task.id] { - handle(eventId, task) +// RegRefreshTaskChildCount 注册任务子计数刷新事件 +func RegRefreshTaskChildCount(taskType int, handler RefreshTaskChildCountEvent) { + refreshTaskChildCountEventHandles[taskType] = handler +} + +// OnRefreshTaskChildCount 触发任务子计数刷新事件 +func OnRefreshTaskChildCount(taskType int, key any, increase int64) { + if handler, ok := refreshTaskChildCountEventHandles[taskType]; ok { + handler(taskType, key, increase) } } diff --git a/game/task/options.go b/game/task/options.go index 16d949e..2182ae2 100644 --- a/game/task/options.go +++ b/game/task/options.go @@ -1,17 +1,85 @@ package task +import ( + "github.com/kercylan98/minotaur/utils/offset" + "time" +) + type Option func(task *Task) -// WithInitCount 通过初始化计数的方式创建任务 -func WithInitCount(count int) Option { +// WithChildCount 通过初始化子计数的方式创建任务 +func WithChildCount(key any, childCount int64) Option { return func(task *Task) { - task.count = count + if task.childCount == nil { + task.childCount = make(map[any]int64) + } + if task.childCondition == nil { + task.childCondition = make(map[any]int64) + } + task.childCount[key] = childCount } } -// WithDone 通过指定任务完成计数的方式创建任务 -func WithDone(done int) Option { +// WithChild 通过指定子计数的方式创建任务 +// - 只有当子计数与主计数均达到条件时,任务才会完成 +// - 通常用于多条件的任务 +func WithChild(key any, childCondition int64) Option { return func(task *Task) { - task.done = done + if task.childCount == nil { + task.childCount = make(map[any]int64) + } + if task.childCondition == nil { + task.childCondition = make(map[any]int64) + } + task.childCondition[key] = childCondition + } +} + +// WithDisableNotStartGetReward 禁止未开始的任务领取奖励 +func WithDisableNotStartGetReward() Option { + return func(task *Task) { + task.disableNotStartGetReward = true + } +} + +// WithCount 通过初始化计数的方式创建任务 +func WithCount(count int64) Option { + return func(task *Task) { + task.SetCount(count) + } +} + +// WithStartTime 通过指定开始时间的方式创建任务 +// - 只有当时间在开始时间之后,任务才会开始计数 +func WithStartTime(startTime time.Time) Option { + return func(task *Task) { + task.start = startTime + } +} + +// WithOffsetTime 通过指定偏移时间的方式创建任务 +func WithOffsetTime(offset *offset.Time) Option { + return func(task *Task) { + task.offset = offset + } +} + +// WithLimitedTime 通过限时的方式创建任务 +func WithLimitedTime(limitTime time.Duration) Option { + return func(task *Task) { + task.limitTime = limitTime + } +} + +// WithFront 通过指定任务前置任务的方式创建任务 +// - 当前置任务未完成时,当前任务不会开始计数 +func WithFront(fronts ...*Task) Option { + return func(task *Task) { + if task.fronts == nil { + task.fronts = make(map[int64]*Task) + } + for _, front := range fronts { + task.fronts[front.GetID()] = front + } } } diff --git a/game/task/state.go b/game/task/state.go index 75d6d07..f2a8807 100644 --- a/game/task/state.go +++ b/game/task/state.go @@ -1,10 +1,10 @@ package task const ( - StateDoing State = iota // 进行中 - StateDone // 已完成 + StateAccept State = iota // 已接受 + StateFinish // 已完成 StateReward // 已领取 + StateFail // 已失败 ) -// State 任务状态 -type State byte +type State uint16 diff --git a/game/task/task.go b/game/task/task.go index 361b245..2130942 100644 --- a/game/task/task.go +++ b/game/task/task.go @@ -1,67 +1,204 @@ package task -// NewTask 新建任务 -func NewTask(id int64, options ...Option) *Task { +import ( + "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/offset" + "time" +) + +// NewTask 创建任务 +func NewTask(id int64, taskType int, condition int64, options ...Option) *Task { task := &Task{ - id: id, + id: id, + condition: condition, + state: StateAccept, } for _, option := range options { option(task) } - if task.count > task.done { - task.count = task.done + if task.start.IsZero() { + if task.offset != nil { + task.start = task.offset.Now() + } else { + task.start = time.Now() + } + } + for key := range task.childCount { + if !hash.Exist(task.childCondition, key) { + delete(task.childCount, key) + } + } + if task.count == task.condition { + task.state = StateFinish } - task.Add(0) return task } +// Task 通用任务数据结构 type Task struct { - id int64 // 任务ID - count int // 任务计数 - done int // 任务完成计数 - reward bool // 是否已领取奖励 + id int64 // 任务ID + taskType int // 任务类型 + count int64 // 任务主计数 + condition int64 // 任务完成需要的计数条件 + childCount map[any]int64 // 任务子计数 + childCondition map[any]int64 // 任务子计数条件 + state State // 任务状态 + start time.Time // 任务开始时间 + limitTime time.Duration // 任务限时 + fronts map[int64]*Task // 任务前置任务 + disableNotStartGetReward bool // 禁止未开始的任务领取奖励 + + offset *offset.Time // 任务偏移时间 +} + +// GetID 获取任务ID +func (slf *Task) GetID() int64 { + return slf.id +} + +// GetType 获取任务类型 +func (slf *Task) GetType() int { + return slf.taskType } // Reset 重置任务 func (slf *Task) Reset() { slf.count = 0 - slf.reward = false - switch slf.GetState() { - case StateDone: - OnTaskDoneEvent(slf) + slf.state = StateAccept + for key := range slf.childCount { + delete(slf.childCount, key) } } -// Add 增加任务计数 -func (slf *Task) Add(count int) { - if count != 0 { - slf.count += count - if slf.count < 0 { - slf.count = 0 - } else if slf.count > slf.done { - slf.count = slf.done +// GetFronts 获取前置任务 +func (slf *Task) GetFronts() map[int64]*Task { + return slf.fronts +} + +// GetFrontsWithState 获取特定状态的前置任务 +func (slf *Task) GetFrontsWithState(state State) map[int64]*Task { + fronts := make(map[int64]*Task) + for id, front := range slf.fronts { + if front.GetState() == state { + fronts[id] = front } } - switch slf.GetState() { - case StateDone: - OnTaskDoneEvent(slf) + return fronts +} + +// FrontsIsFinish 判断前置任务是否完成 +func (slf *Task) FrontsIsFinish() bool { + for _, front := range slf.fronts { + state := front.GetState() + if state == StateAccept || state == StateFail { + return false + } } + return true +} + +// GetReward 获取任务奖励 +// - 当任务状态为 StateFinish 时,调用 rewardHandle 函数 +// - 当任务状态不为 StateFinish 或奖励函数发生错误时,返回错误 +func (slf *Task) GetReward(rewardHandle func() error) error { + if !slf.IsStart() { + return ErrTaskNotStart + } + switch slf.GetState() { + case StateAccept: + return ErrTaskNotFinish + case StateReward: + return ErrTaskRewardReceived + case StateFail: + return ErrTaskFail + } + if err := rewardHandle(); err != nil { + return err + } + slf.state = StateReward + return nil } // GetState 获取任务状态 func (slf *Task) GetState() State { - if slf.count >= slf.done { - if slf.reward { - return StateReward - } - return StateDone - } - return StateDoing + return slf.state } -// Reward 返回是否领取过奖励,并且设置任务为领取过奖励的状态 -func (slf *Task) Reward() bool { - reward := slf.reward - slf.reward = true - return reward +// IsStart 判断任务是否开始 +func (slf *Task) IsStart() bool { + var current time.Time + if slf.offset != nil { + current = slf.offset.Now() + } else { + current = time.Now() + } + if current.Before(slf.start) { + return false + } else if slf.limitTime > 0 && current.Sub(slf.start) >= slf.limitTime { + return false + } + return true +} + +// SetCount 设置计数 +func (slf *Task) SetCount(count int64) { + if !slf.IsStart() || !slf.FrontsIsFinish() { + return + } + slf.count = count + if slf.count >= slf.condition { + slf.count = slf.condition + } else if slf.count < 0 { + slf.count = 0 + } + slf.refreshState() +} + +// AddCount 增加计数 +func (slf *Task) AddCount(count int64) { + slf.SetCount(slf.count + count) +} + +// GetCount 获取计数 +func (slf *Task) GetCount() int64 { + return slf.count +} + +// GetCondition 获取计数条件 +func (slf *Task) GetCondition() int64 { + return slf.condition +} + +// SetChildCount 设置子计数 +func (slf *Task) SetChildCount(key any, count int64) { + if !slf.IsStart() || !slf.FrontsIsFinish() || !hash.Exist(slf.childCondition, key) { + return + } + if condition := slf.childCondition[key]; count > condition { + count = condition + } else if count < 0 { + count = 0 + } + slf.childCount[key] = count + slf.refreshState() +} + +// AddChildCount 增加子计数 +func (slf *Task) AddChildCount(key any, count int64) { + slf.SetChildCount(key, slf.childCount[key]+count) +} + +// refreshState 刷新任务状态 +func (slf *Task) refreshState() { + slf.state = StateFinish + if slf.count != slf.condition { + slf.state = StateAccept + return + } + for key, condition := range slf.childCondition { + if slf.childCount[key] != condition { + slf.state = StateAccept + return + } + } } diff --git a/utils/maths/math.go b/utils/maths/math.go index 6fb118e..03ef7f4 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -8,6 +8,7 @@ import ( const ( DefaultTolerance = 0.0001 // 默认误差范围 + Zero = 0 // 零 ) // GetDefaultTolerance 获取默认误差范围 From 47b8a333eb22c6bbfe1c6e533681c7dcb5ca34fd Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 11 Jul 2023 19:35:08 +0800 Subject: [PATCH 2/8] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BB=93=E6=9E=84=EF=BC=8C=E5=8E=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=8E=BB=E9=99=A4=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/activity.go | 6 ----- server/constants.go | 5 ++++ server/cross/nats.go | 10 ++++--- server/multiple.go | 6 ++--- server/server.go | 8 +++--- utils/asynchronization/map.go | 4 --- utils/geometry/errors.go | 8 ++++++ utils/geometry/geometry.go | 6 ++--- utils/hash/map_readonly.go | 1 - utils/str/transform.go | 39 ++++++++++++++-------------- utils/synchronization/map.go | 8 ------ utils/synchronization/map_segment.go | 6 ----- 12 files changed, 49 insertions(+), 58 deletions(-) create mode 100644 utils/geometry/errors.go diff --git a/game/activity/activity.go b/game/activity/activity.go index 55b6fd6..bb4ebee 100644 --- a/game/activity/activity.go +++ b/game/activity/activity.go @@ -122,12 +122,6 @@ func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsInvalid() bool { current := activityOffset.Now() if slf.beforeShow.IsZero() && !slf.afterShow.IsZero() { return current.After(slf.afterShow) || current.Equal(slf.afterShow) - } else if !slf.beforeShow.IsZero() && slf.afterShow.IsZero() { - end := slf.GetEnd() - return current.After(end) || current.Equal(end) - } else if !slf.beforeShow.IsZero() && !slf.afterShow.IsZero() { - end := slf.GetEnd() - return current.After(end) || current.Equal(end) } else { end := slf.GetEnd() return current.After(end) || current.Equal(end) diff --git a/server/constants.go b/server/constants.go index a2aecd9..d7e0585 100644 --- a/server/constants.go +++ b/server/constants.go @@ -2,6 +2,11 @@ package server import "time" +const ( + serverMultipleMark = "Minotaur Multiple Server" + serverMark = "Minotaur Server" +) + const ( DefaultMessageBufferSize = 1024 DefaultMessageChannelSize = 1024 * 4096 diff --git a/server/cross/nats.go b/server/cross/nats.go index a795533..43b2f78 100644 --- a/server/cross/nats.go +++ b/server/cross/nats.go @@ -11,6 +11,10 @@ import ( "time" ) +const ( + nasMark = "Cross.Nats" +) + func NewNats(url string, options ...NatsOption) *Nats { n := &Nats{ url: url, @@ -43,10 +47,10 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p nats.ReconnectWait(time.Second*5), nats.MaxReconnects(-1), nats.DisconnectErrHandler(func(conn *nats.Conn, err error) { - log.Error("Cross.Nats", zap.String("info", "disconnect"), zap.Error(err)) + log.Error(nasMark, zap.String("info", "disconnect"), zap.Error(err)) }), nats.ReconnectHandler(func(conn *nats.Conn) { - log.Info("Cross.Nats", zap.String("info", "reconnect")) + log.Info(nasMark, zap.String("info", "reconnect")) }), ) } @@ -59,7 +63,7 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p message := slf.messagePool.Get() defer slf.messagePool.Release(message) if err := json.Unmarshal(msg.Data, &message); err != nil { - log.Error("Cross.Nats", zap.Error(err)) + log.Error(nasMark, zap.Error(err)) return } packetHandle(message.ServerId, message.Packet) diff --git a/server/multiple.go b/server/multiple.go index f72164e..e3ddad2 100644 --- a/server/multiple.go +++ b/server/multiple.go @@ -60,14 +60,14 @@ func (slf *MultipleServer) Run() { } wait.Wait() - log.Info("Server", zap.String("Minotaur Multiple Server", "====================================================================")) + log.Info("Server", zap.String(serverMultipleMark, "====================================================================")) for _, server := range slf.servers { - log.Info("Server", zap.String("Minotaur Multiple Server", "RunningInfo"), + log.Info("Server", zap.String(serverMultipleMark, "RunningInfo"), zap.Any("network", server.network), zap.String("listen", server.addr), ) } - log.Info("Server", zap.String("Minotaur Multiple Server", "====================================================================")) + log.Info("Server", zap.String(serverMultipleMark, "====================================================================")) systemSignal := make(chan os.Signal, 1) signal.Notify(systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) diff --git a/server/server.go b/server/server.go index 81e59bb..7118d0f 100644 --- a/server/server.go +++ b/server/server.go @@ -259,7 +259,7 @@ func (slf *Server) Run(addr string) error { conn := newWebsocketConn(slf, ws, ip) for k, v := range request.URL.Query() { if len(v) == 1 { - conn.SetData(k, v) + conn.SetData(k, v[0]) } else { conn.SetData(k, v) } @@ -307,12 +307,12 @@ func (slf *Server) Run(addr string) error { } if slf.multiple == nil { - log.Info("Server", zap.String("Minotaur Server", "====================================================================")) - log.Info("Server", zap.String("Minotaur Server", "RunningInfo"), + log.Info("Server", zap.String(serverMark, "====================================================================")) + log.Info("Server", zap.String(serverMark, "RunningInfo"), zap.Any("network", slf.network), zap.String("listen", slf.addr), ) - log.Info("Server", zap.String("Minotaur Server", "====================================================================")) + log.Info("Server", zap.String(serverMark, "====================================================================")) slf.OnStartFinishEvent() signal.Notify(slf.systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) diff --git a/utils/asynchronization/map.go b/utils/asynchronization/map.go index 5082fcc..21b641b 100644 --- a/utils/asynchronization/map.go +++ b/utils/asynchronization/map.go @@ -48,10 +48,6 @@ func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) { return value, exist } -func (slf *Map[Key, Value]) Length() int { - return len(slf.data) -} - func (slf *Map[Key, Value]) Delete(key Key) { delete(slf.data, key) } diff --git a/utils/geometry/errors.go b/utils/geometry/errors.go new file mode 100644 index 0000000..cd4e4b9 --- /dev/null +++ b/utils/geometry/errors.go @@ -0,0 +1,8 @@ +package geometry + +import "errors" + +var ( + // ErrUnexplainedDirection 错误的方向 + ErrUnexplainedDirection = errors.New("unexplained direction") +) diff --git a/utils/geometry/geometry.go b/utils/geometry/geometry.go index 8ff85d1..4ab1fb4 100644 --- a/utils/geometry/geometry.go +++ b/utils/geometry/geometry.go @@ -48,7 +48,7 @@ func GetDirectionNextWithCoordinate[V generic.SignedNumber](direction Direction, case DirectionRight: nx, ny = x+1, y default: - panic("unexplained direction") + panic(ErrUnexplainedDirection) } return } @@ -66,7 +66,7 @@ func GetDirectionNextWithPoint[V generic.SignedNumber](direction Direction, poin case DirectionRight: return NewPoint(x+1, y) default: - panic("unexplained direction") + panic(ErrUnexplainedDirection) } } @@ -83,7 +83,7 @@ func GetDirectionNextWithPos[V generic.SignedNumber](direction Direction, width, case DirectionRight: return pos + 1 default: - panic("unexplained direction") + panic(ErrUnexplainedDirection) } } diff --git a/utils/hash/map_readonly.go b/utils/hash/map_readonly.go index 0a3b28f..67a442d 100644 --- a/utils/hash/map_readonly.go +++ b/utils/hash/map_readonly.go @@ -5,7 +5,6 @@ type MapReadonly[Key comparable, Value any] interface { Get(key Key) Value Exist(key Key) bool GetExist(key Key) (Value, bool) - Length() int Range(handle func(key Key, value Value)) RangeSkip(handle func(key Key, value Value) bool) RangeBreakout(handle func(key Key, value Value) bool) diff --git a/utils/str/transform.go b/utils/str/transform.go index 39824e9..8aa01f1 100644 --- a/utils/str/transform.go +++ b/utils/str/transform.go @@ -17,12 +17,12 @@ func HideSensitivity(str string) (result string) { if len(res[0]) < 3 { resString := "***" result = resString + "@" + res[1] - } else { - resRs := []rune(str) - res2 := string(resRs[0:3]) - resString := res2 + "***" - result = resString + "@" + res[1] + return result } + resRs := []rune(str) + res2 := string(resRs[0:3]) + resString := res2 + "***" + result = resString + "@" + res[1] return result } else { reg := `^1[0-9]\d{9}$` @@ -31,22 +31,21 @@ func HideSensitivity(str string) (result string) { if mobileMatch { rs := []rune(str) result = string(rs[0:5]) + "****" + string(rs[7:11]) + return + } + nameRune := []rune(str) + lens := len(nameRune) - } else { - nameRune := []rune(str) - lens := len(nameRune) - - if lens <= 1 { - result = "***" - } else if lens == 2 { - result = string(nameRune[:1]) + "*" - } else if lens == 3 { - result = string(nameRune[:1]) + "*" + string(nameRune[2:3]) - } else if lens == 4 { - result = string(nameRune[:1]) + "**" + string(nameRune[lens-1:lens]) - } else if lens > 4 { - result = string(nameRune[:2]) + "***" + string(nameRune[lens-2:lens]) - } + if lens <= 1 { + result = "***" + } else if lens == 2 { + result = string(nameRune[:1]) + "*" + } else if lens == 3 { + result = string(nameRune[:1]) + "*" + string(nameRune[2:3]) + } else if lens == 4 { + result = string(nameRune[:1]) + "**" + string(nameRune[lens-1:lens]) + } else if lens > 4 { + result = string(nameRune[:2]) + "***" + string(nameRune[lens-2:lens]) } return } diff --git a/utils/synchronization/map.go b/utils/synchronization/map.go index d2ad468..a97bc92 100644 --- a/utils/synchronization/map.go +++ b/utils/synchronization/map.go @@ -74,14 +74,6 @@ func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) { return value, exist } -func (slf *Map[Key, Value]) Length() int { - if !slf.atom { - slf.lock.RLock() - defer slf.lock.RUnlock() - } - return len(slf.data) -} - func (slf *Map[Key, Value]) Delete(key Key) { if !slf.atom { slf.lock.Lock() diff --git a/utils/synchronization/map_segment.go b/utils/synchronization/map_segment.go index 3182fa2..853bd44 100644 --- a/utils/synchronization/map_segment.go +++ b/utils/synchronization/map_segment.go @@ -87,12 +87,6 @@ func (slf *MapSegment[Key, Value]) GetExist(key Key) (value Value, exist bool) { return slf.segments[s].GetExist(key) } -func (slf *MapSegment[Key, Value]) Length() int { - slf.lock.RLock() - defer slf.lock.RUnlock() - return len(slf.cache) -} - func (slf *MapSegment[Key, Value]) Delete(key Key) { slf.lock.Lock() s, exist := slf.cache[key] From 8917326a246d52599ef5de18f939a3e035c245db Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 11 Jul 2023 19:38:50 +0800 Subject: [PATCH 3/8] =?UTF-8?q?other:=20=E4=BF=AE=E6=94=B9=20server.Server?= =?UTF-8?q?=20=E6=85=A2=E6=B6=88=E6=81=AF=E6=A3=80=E6=B5=8B=E7=9A=84?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E6=B6=88=E6=81=AF=E5=88=A4=E5=AE=9A=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E4=B8=BA=201=20=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/server.go b/server/server.go index 7118d0f..2365a54 100644 --- a/server/server.go +++ b/server/server.go @@ -479,9 +479,9 @@ func (slf *Server) pushMessage(message *Message) { slf.messageChannel <- message } -func (slf *Server) low(message *Message, present time.Time) { +func (slf *Server) low(message *Message, present time.Time, expect time.Duration) { cost := time.Since(present) - if cost > time.Millisecond*100 { + if cost > expect { log.Warn("Server", zap.String("LowExecCost", cost.String()), zap.Any("Message", message)) slf.OnMessageLowExecEvent(message, cost) } @@ -516,7 +516,7 @@ func (slf *Server) dispatchMessage(msg *Message) { if msg.t != MessageTypeAsync { super.Handle(cancel) - slf.low(msg, present) + slf.low(msg, present, time.Millisecond*100) if !slf.isShutdown.Load() { slf.messagePool.Release(msg) } @@ -555,7 +555,7 @@ func (slf *Server) dispatchMessage(msg *Message) { } } super.Handle(cancel) - slf.low(msg, present) + slf.low(msg, present, time.Second) if !slf.isShutdown.Load() { slf.messagePool.Release(msg) } From a4a27ea9da7d1d61ad1a5972077f888f309e8f4d Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 12 Jul 2023 17:45:30 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=E5=8F=AF=E4=BD=BF=E7=94=A8=20super?= =?UTF-8?q?.NewStackGo=20=E5=88=9B=E5=BB=BA=E7=94=A8=E4=BA=8E=E5=AF=B9?= =?UTF-8?q?=E4=B8=8A=E4=B8=80=E4=B8=AA=E5=8D=8F=E7=A8=8B=E5=A0=86=E6=A0=88?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E6=94=B6=E9=9B=86=E7=9A=84=E6=94=B6=E9=9B=86?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/stack.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 utils/super/stack.go diff --git a/utils/super/stack.go b/utils/super/stack.go new file mode 100644 index 0000000..2458695 --- /dev/null +++ b/utils/super/stack.go @@ -0,0 +1,50 @@ +package super + +import ( + "runtime/debug" +) + +// NewStackGo 返回一个用于获取上一个协程调用的堆栈信息的收集器 +func NewStackGo() *StackGo { + return new(StackGo) +} + +// StackGo 用于获取上一个协程调用的堆栈信息 +// - 应当最先运行 Wait 函数,然后在其他协程中调用 Stack 函数或者 GiveUp 函数 +type StackGo struct { + stack chan *struct{} // 消息堆栈 + collect chan []byte // 消息堆栈收集 +} + +// Wait 等待收集消息堆栈 +// - 在调用 Wait 函数后,当前协程将会被挂起,直到调用 Stack 或 GiveUp 函数 +func (slf *StackGo) Wait() { + slf.stack = make(chan *struct{}, 0) + if s := <-slf.stack; s != nil { + slf.collect <- debug.Stack() + close(slf.collect) + slf.collect = nil + } + close(slf.stack) + slf.stack = nil +} + +// Stack 获取消息堆栈 +// - 在调用 Wait 函数后调用该函数,将会返回上一个协程的堆栈信息 +// - 在调用 GiveUp 函数后调用该函数,将会 panic +func (slf *StackGo) Stack() []byte { + slf.stack <- &struct{}{} + slf.collect = make(chan []byte, 1) + stack := <-slf.collect + slf.stack = nil + return stack +} + +// GiveUp 放弃收集消息堆栈 +// - 在调用 Wait 函数后调用该函数,将会放弃收集消息堆栈并且释放资源 +// - 在调用 GiveUp 函数后调用 Stack 函数,将会 panic +func (slf *StackGo) GiveUp() { + if slf.stack != nil { + slf.stack <- nil + } +} From 94147e8b9c99de298cc6a6d8957f286f9409f54f Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 12 Jul 2023 17:46:13 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20str=20=E5=8C=85=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=86=85=E7=BD=AE=E5=AD=97=E7=AC=A6=20Dunno=E3=80=81CenterDot?= =?UTF-8?q?=E3=80=81Dot=E3=80=81Slash=20=E5=92=8C=E5=85=B6=20[]byte=20?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/str/str.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/utils/str/str.go b/utils/str/str.go index f433268..33e8d7b 100644 --- a/utils/str/str.go +++ b/utils/str/str.go @@ -1,7 +1,19 @@ package str const ( - None = "" // 空字符串 + None = "" // 空字符串 + Dunno = "?" // 未知 + CenterDot = "·" // 中点 + Dot = "." // 点 + Slash = "/" // 斜杠 +) + +var ( + NoneBytes = []byte("") // 空字符串 + DunnoBytes = []byte("?") // 未知 + CenterDotBytes = []byte("·") // 中点 + DotBytes = []byte(".") // 点 + SlashBytes = []byte("/") // 斜杠 ) // FirstUpper 首字母大写 From 98234e5f861cecfe4fd30f3db51713201d19c725 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 12 Jul 2023 18:34:45 +0800 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20log=20=E5=8C=85=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BD=BF=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/constants.go | 15 ++- server/message.go | 15 +-- server/options.go | 8 +- server/server.go | 63 +++++----- utils/hash/convert.go | 9 ++ utils/log/core.go | 5 + utils/log/encoder.go | 26 ++++ utils/log/field.go | 159 +++++++++++++++++++++++++ utils/log/level.go | 110 +++++++++++++++++ utils/log/log.go | 270 ++++++++++++++++++++---------------------- utils/log/logger.go | 79 ++++++++++++ utils/log/options.go | 52 ++++++++ utils/log/run_mode.go | 16 +++ 13 files changed, 637 insertions(+), 190 deletions(-) create mode 100644 utils/log/core.go create mode 100644 utils/log/encoder.go create mode 100644 utils/log/field.go create mode 100644 utils/log/level.go create mode 100644 utils/log/logger.go create mode 100644 utils/log/options.go create mode 100644 utils/log/run_mode.go diff --git a/server/constants.go b/server/constants.go index d7e0585..1df8385 100644 --- a/server/constants.go +++ b/server/constants.go @@ -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" diff --git a/server/message.go b/server/message.go index 293991d..f9a098e 100644 --- a/server/message.go +++ b/server/message.go @@ -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) } diff --git a/server/options.go b/server/options.go index 15b5166..a7ad9d6 100644 --- a/server/options.go +++ b/server/options.go @@ -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 } } diff --git a/server/server.go b/server/server.go index 2365a54..3bc658b 100644 --- a/server/server.go +++ b/server/server.go @@ -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,18 +499,21 @@ 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 { - super.Handle(cancel) - slf.low(msg, present, time.Millisecond*100) - if !slf.isShutdown.Load() { - slf.messagePool.Release(msg) - } + if msg.t == MessageTypeAsync { + return + } + + super.Handle(cancel) + slf.low(msg, present, time.Millisecond*100) + + if !slf.isShutdown.Load() { + slf.messagePool.Release(msg) } }() @@ -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) } diff --git a/utils/hash/convert.go b/utils/hash/convert.go index a12a7e5..35113ff 100644 --- a/utils/hash/convert.go +++ b/utils/hash/convert.go @@ -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 将切片转换为 map,value作为Key func ToMapBool[V comparable](slice []V) map[V]bool { var m = make(map[V]bool) diff --git a/utils/log/core.go b/utils/log/core.go new file mode 100644 index 0000000..5d8965e --- /dev/null +++ b/utils/log/core.go @@ -0,0 +1,5 @@ +package log + +import "go.uber.org/zap/zapcore" + +type Core = zapcore.Core diff --git a/utils/log/encoder.go b/utils/log/encoder.go new file mode 100644 index 0000000..c7d5570 --- /dev/null +++ b/utils/log/encoder.go @@ -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) + }, + }) +} diff --git a/utils/log/field.go b/utils/log/field.go new file mode 100644 index 0000000..5919e66 --- /dev/null +++ b/utils/log/field.go @@ -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 序列化为 JSON),Reflect 将在最终日志输出中包含错误消息 + 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 +) diff --git a/utils/log/level.go b/utils/log/level.go new file mode 100644 index 0000000..b1dac5b --- /dev/null +++ b/utils/log/level.go @@ -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 + } +} diff --git a/utils/log/log.go b/utils/log/log.go index cfab117..bc77bd2 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -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")) +// NewLog 创建一个日志记录器 +func NewLog(options ...Option) *Log { + log := &Log{ + filename: func(level Level) string { + return fmt.Sprintf("%s.log", level.String()) + }, + rotateFilename: func(level Level) string { + return strings.Join([]string{level.String(), "%Y%m%d.log"}, ".") + }, + levelPartition: defaultLevelPartition, } -} -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)) - }, - CallerKey: "file", - EncodeCaller: zapcore.ShortCallerEncoder, - EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) { - enc.AppendInt64(int64(d) / 1000000) - }, - }) + for _, option := range options { + option(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), - ) - } - } 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), - ) + 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)), - ) - - if err != nil { - panic(err) + 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) + } + 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 < 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 }))) + } + } + } } - return hook + + log.zap = zap.New(zapcore.NewTee(log.cores...), zap.AddCaller(), zap.AddCallerSkip(1)) + log.sugar = log.zap.Sugar() + return log } -type MLogger struct { - *zap.Logger +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 } -func (slf *MLogger) Printf(format string, args ...interface{}) { - slf.Info(fmt.Sprintf(format, args...)) +func (slf *Log) Debugf(format string, args ...interface{}) { + slf.sugar.Debugf(format, args...) } -func Logger() *MLogger { - return &MLogger{logger} +func (slf *Log) Infof(format string, args ...interface{}) { + slf.sugar.Infof(format, args...) } -func Info(msg string, fields ...zap.Field) { - logger.Info(msg, fields...) +func (slf *Log) Warnf(format string, args ...interface{}) { + slf.sugar.Warnf(format, args...) } -func Warn(msg string, fields ...zap.Field) { - logger.Warn(msg, fields...) +func (slf *Log) Errorf(format string, args ...interface{}) { + slf.sugar.Errorf(format, args...) } -func Debug(msg string, fields ...zap.Field) { - logger.Debug(msg, fields...) +func (slf *Log) Fatalf(format string, args ...interface{}) { + slf.sugar.Fatalf(format, args...) } -func Error(msg string, fields ...zap.Field) { - logger.Error(msg, fields...) - fmt.Println(string(debug.Stack())) +func (slf *Log) Printf(format string, args ...interface{}) { + slf.sugar.Infof(format, args...) } -func ErrorHideStack(msg string, fields ...zap.Field) { - logger.Error(msg, fields...) +// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +func (slf *Log) Debug(msg string, fields ...Field) { + slf.zap.Debug(msg, fields...) } -// ErrorWithStack 通过额外的堆栈信息打印错误日志 -func ErrorWithStack(msg, stack string, fields ...zap.Field) { - logger.Error(msg, fields...) - var stackMerge string - if len(stack) > 0 { - stackMerge = stack - } - stackMerge += string(debug.Stack()) - fmt.Println(stackMerge) +// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +func (slf *Log) Info(msg string, fields ...Field) { + slf.zap.Info(msg, fields...) } -// SetProd 设置生产环境模式 -func SetProd(isProd bool) { - if prod == isProd { - return - } - prod = isProd - if logger != nil { - _ = logger.Sync() - } - logger = newLogger() +// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +func (slf *Log) Warn(msg string, fields ...Field) { + slf.zap.Warn(msg, fields...) } -// SetLogDir 设置日志输出目录 -func SetLogDir(dir string) { - logPath = dir - if logger != nil { - _ = logger.Sync() - } - logger = newLogger() +// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +func (slf *Log) Error(msg string, fields ...Field) { + slf.zap.Error(msg, fields...) } -// SetWriteFileWithDev 设置开发环境下写入文件 -func SetWriteFileWithDev(isWrite bool) { - if isWrite == logDevWrite { - return - } - logDevWrite = isWrite - if logger != nil { - _ = logger.Sync() - } - logger = newLogger() +// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +// - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“development panic”)。这对于捕获可恢复但不应该发生的错误很有用 +func (slf *Log) DPanic(msg string, fields ...Field) { + slf.zap.DPanic(msg, fields...) } -// SetLogRotate 设置日志切割时间 -func SetLogRotate(t int) { - logTime = t - if logger != nil { - _ = logger.Sync() - } - logger = newLogger() +// 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...) } diff --git a/utils/log/logger.go b/utils/log/logger.go new file mode 100644 index 0000000..a0a1c96 --- /dev/null +++ b/utils/log/logger.go @@ -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 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 + // - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“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 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段 +// - 如果记录器处于开发模式,它就会出现 panic(DPanic 的意思是“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 +} diff --git a/utils/log/options.go b/utils/log/options.go new file mode 100644 index 0000000..c00297c --- /dev/null +++ b/utils/log/options.go @@ -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 + } +} diff --git a/utils/log/run_mode.go b/utils/log/run_mode.go new file mode 100644 index 0000000..b49a7d4 --- /dev/null +++ b/utils/log/run_mode.go @@ -0,0 +1,16 @@ +package log + +const ( + // RunModeDev 开发模式是默认的运行模式,同时也是最基础的运行模式 + // - 开发模式下,将会输出所有级别的日志到控制台 + // - 默认不再输出日志到文件 + RunModeDev RunMode = iota + // RunModeTest 测试模式是一种特殊的运行模式,用于测试 + // - 测试模式下,将会输出所有级别的日志到控制台和文件 + RunModeTest + // RunModeProd 生产模式是一种特殊的运行模式,用于生产 + // - 生产模式下,将会输出 InfoLevel 及以上级别的日志到控制台和文件 + RunModeProd +) + +type RunMode uint8 From b5a4bc959df8aee316bedd4050ac34d77a858162 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 12 Jul 2023 18:36:47 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20super.StackGO=20=E8=BF=9B=E8=A1=8C=E8=B7=A8?= =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E5=90=8C=E6=AD=A5=E8=BF=90=E8=A1=8C=E5=A0=86?= =?UTF-8?q?=E6=A0=88=E6=8A=93=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/stack.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utils/super/stack.go b/utils/super/stack.go index 2458695..760d469 100644 --- a/utils/super/stack.go +++ b/utils/super/stack.go @@ -11,6 +11,7 @@ func NewStackGo() *StackGo { // StackGo 用于获取上一个协程调用的堆栈信息 // - 应当最先运行 Wait 函数,然后在其他协程中调用 Stack 函数或者 GiveUp 函数 +// - 适用于跨协程同步通讯,例如单线程的消息处理统计耗时打印堆栈信息 type StackGo struct { stack chan *struct{} // 消息堆栈 collect chan []byte // 消息堆栈收集 @@ -22,8 +23,6 @@ func (slf *StackGo) Wait() { slf.stack = make(chan *struct{}, 0) if s := <-slf.stack; s != nil { slf.collect <- debug.Stack() - close(slf.collect) - slf.collect = nil } close(slf.stack) slf.stack = nil @@ -33,10 +32,14 @@ func (slf *StackGo) Wait() { // - 在调用 Wait 函数后调用该函数,将会返回上一个协程的堆栈信息 // - 在调用 GiveUp 函数后调用该函数,将会 panic func (slf *StackGo) Stack() []byte { - slf.stack <- &struct{}{} + if slf.stack == nil { + return nil + } slf.collect = make(chan []byte, 1) + slf.stack <- &struct{}{} stack := <-slf.collect - slf.stack = nil + close(slf.collect) + slf.collect = nil return stack } From aa39d391606b0a1817b16886616d8803925c90cf Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 12 Jul 2023 19:14:51 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=B6=88=E6=81=AF=E6=8A=A5=E9=94=99=E4=B8=8D=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E5=A0=86=E6=A0=88=E4=BF=A1=E6=81=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 3bc658b..162aafe 100644 --- a/server/server.go +++ b/server/server.go @@ -20,6 +20,7 @@ import ( "net/http" "os" "os/signal" + "runtime/debug" "strings" "sync/atomic" "syscall" @@ -499,7 +500,9 @@ 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), zap.Stack("stack")) + stack := string(debug.Stack()) + log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err), zap.String("stack", stack)) + fmt.Println(stack) if e, ok := err.(error); ok { slf.OnMessageErrorEvent(msg, e) } @@ -543,7 +546,9 @@ 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("error", err), zap.Stack("stack")) + stack := string(debug.Stack()) + log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("error", err), zap.String("stack", stack)) + fmt.Println(stack) if e, ok := err.(error); ok { slf.OnMessageErrorEvent(msg, e) }