refactor: slog替换logrus、日志操作统一、支持json、text格式等

This commit is contained in:
meilin.huang
2023-09-02 17:24:18 +08:00
parent d51cd4b289
commit 899a3a8243
47 changed files with 685 additions and 293 deletions

View File

@@ -33,8 +33,12 @@ mysql:
# password: 111049
# db: 0
log:
# 日志等级, trace, debug, info, warn, error, fatal
# 日志等级, debug, info, warn, error
level: info
# 日志格式类型, text/json
type: text
# 是否记录方法调用栈信息
add-source: false
# file:
# path: ./
# name: mayfly-go.log

View File

@@ -21,7 +21,6 @@ require (
github.com/pquerna/otp v1.4.0
github.com/redis/go-redis/v9 v9.1.0
github.com/robfig/cron/v3 v3.0.1 //
github.com/sirupsen/logrus v1.9.3
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.12.1 // mongo
golang.org/x/crypto v0.12.0 // ssh

View File

@@ -32,7 +32,7 @@ func PwdAesEncrypt(password string) string {
return ""
}
aes := config.Conf.Aes
if aes == nil {
if aes.Key == "" {
return password
}
encryptPwd, err := aes.EncryptBase64([]byte(password))
@@ -46,7 +46,7 @@ func PwdAesDecrypt(encryptPwd string) string {
return ""
}
aes := config.Conf.Aes
if aes == nil {
if aes.Key == "" {
return encryptPwd
}
decryptPwd, err := aes.DecryptBase64(encryptPwd)

View File

@@ -9,7 +9,7 @@ import (
"mayfly-go/internal/machine/infrastructure/machine"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
@@ -201,7 +201,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
DB, err := GetDbConn(d, db)
if err != nil {
dbi.Close()
global.Log.Errorf("连接db失败: %s:%d/%s", d.Host, d.Port, db)
logx.Errorf("连接db失败: %s:%d/%s", d.Host, d.Port, db)
panic(biz.NewBizErr(fmt.Sprintf("数据库连接失败: %s", err.Error())))
}
@@ -213,7 +213,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
DB.SetMaxIdleConns(1)
dbi.db = DB
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, db)
logx.Infof("连接db: %s:%d/%s", d.Host, d.Port, db)
if needCache {
dbCache.Put(cacheKey, dbi)
}
@@ -285,7 +285,7 @@ func (di *DbInstance) GetMeta() DbMetadata {
func (d *DbInstance) Close() {
if d.db != nil {
if err := d.db.Close(); err != nil {
global.Log.Errorf("关闭数据库实例[%s]连接失败: %s", d.Id, err.Error())
logx.Errorf("关闭数据库实例[%s]连接失败: %s", d.Id, err.Error())
}
d.db = nil
}
@@ -297,7 +297,7 @@ func (d *DbInstance) Close() {
var dbCache = cache.NewTimedCache(consts.DbConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key any, value any) {
global.Log.Info(fmt.Sprintf("删除db连接缓存 id = %s", key))
logx.Info(fmt.Sprintf("删除db连接缓存 id = %s", key))
value.(*DbInstance).Close()
})

View File

@@ -84,7 +84,7 @@ func (d *dbSqlExecAppImpl) Exec(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
stmt, err := sqlparser.Parse(sql)
if err != nil {
// 就算解析失败也执行sql让数据库来判断错误。如果是查询sql则简单判断是否有limit分页参数信息兼容pgsql
// global.Log.Warnf("sqlparse解析sql[%s]失败: %s", sql, err.Error())
// logx.Warnf("sqlparse解析sql[%s]失败: %s", sql, err.Error())
lowerSql := strings.ToLower(execSqlReq.Sql)
isSelect := strings.HasPrefix(lowerSql, "select")
if isSelect {

View File

@@ -3,7 +3,7 @@ package application
import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/rediscli"
"mayfly-go/pkg/scheduler"
@@ -154,7 +154,7 @@ func (m *machineCropJobAppImpl) MachineRelateCronJobs(machineId uint64, cronJobs
func (m *machineCropJobAppImpl) InitCronJob() {
defer func() {
if err := recover(); err != nil {
global.Log.Errorf("机器计划任务初始化失败: %s", err.(error).Error())
logx.Errorf("机器计划任务初始化失败: %s", err.(error).Error())
}
}()
@@ -241,7 +241,7 @@ func (m *machineCropJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineC
Status: entity.MachineCronJobExecStatusError,
Res: res,
})
global.Log.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
}
}()
@@ -250,9 +250,9 @@ func (m *machineCropJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineC
if res == "" {
res = err.Error()
}
global.Log.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
} else {
global.Log.Debugf("机器:[%d]执行[%s]计划任务成功, 执行结果: %s", mid, cronJob.Name, res)
logx.Debugf("机器:[%d]执行[%s]计划任务成功, 执行结果: %s", mid, cronJob.Name, res)
}
if cronJob.SaveExecResType == entity.SaveExecResTypeNo ||

View File

@@ -8,7 +8,6 @@ import (
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/internal/machine/infrastructure/machine"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/model"
"os"
"strings"
@@ -93,9 +92,9 @@ func (m *machineFileAppImpl) Save(entity *entity.MachineFile) {
biz.NotNil(m.machineRepo.GetById(entity.MachineId, "Name"), "该机器不存在")
if entity.Id != 0 {
gormx.UpdateById(entity)
m.machineFileRepo.UpdateById(entity)
} else {
gormx.Insert(entity)
m.machineFileRepo.Create(entity)
}
}

View File

@@ -7,7 +7,7 @@ import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"net"
"time"
@@ -72,7 +72,7 @@ func (c *Cli) connect() error {
// 关闭client并从缓存中移除如果使用隧道则也关闭
func (c *Cli) Close() {
m := c.machine
global.Log.Info(fmt.Sprintf("关闭机器客户端连接-> id: %d, name: %s, ip: %s", m.Id, m.Name, m.Ip))
logx.Info(fmt.Sprintf("关闭机器客户端连接-> id: %d, name: %s, ip: %s", m.Id, m.Name, m.Ip))
if c.client != nil {
c.client.Close()
c.client = nil
@@ -173,7 +173,10 @@ func DeleteCli(id uint64) {
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *Info) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(_ any) (any, error) {
if load, ok := cliCache.Get(machineId); ok {
return load.(*Cli), nil
}
me := getMachine(machineId)
err := IfUseSshTunnelChangeIpPort(me, getMachine)
if err != nil {
@@ -185,13 +188,9 @@ func GetCli(machineId uint64, getMachine func(uint64) *Info) (*Cli, error) {
return nil, err
}
c.sshTunnelMachineId = me.SshTunnelMachineId
return c, nil
})
if cli != nil {
return cli.(*Cli), err
}
return nil, err
cliCache.Put(machineId, c)
return c, nil
}
// 测试连接使用传值的方式而非引用。因为如果使用了ssh隧道则ip和端口会变为本地映射地址与端口
@@ -276,7 +275,7 @@ func newClient(machine *Info) (*Cli, error) {
return nil, errors.New("机器不存在")
}
global.Log.Infof("[%s]机器连接:%s:%d", machine.Name, machine.Ip, machine.Port)
logx.Infof("[%s]机器连接:%s:%d", machine.Name, machine.Ip, machine.Port)
cli := new(Cli)
cli.machine = machine
err := cli.connect()

View File

@@ -3,7 +3,7 @@ package machine
import (
"fmt"
"io"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/scheduler"
"mayfly-go/pkg/utils/netx"
"net"
@@ -29,7 +29,7 @@ var (
type CheckSshTunnelMachineHasUseFunc func(int) bool
func startCheckUse() {
global.Log.Info("开启定时检测ssh隧道机器是否还有被使用")
logx.Info("开启定时检测ssh隧道机器是否还有被使用")
// 每十分钟检查一次隧道机器是否还有被使用
scheduler.AddFun("@every 10m", func() {
if !mutex.TryLock() {
@@ -38,7 +38,7 @@ func startCheckUse() {
defer mutex.Unlock()
// 遍历隧道机器,都未被使用将会被关闭
for mid, sshTunnelMachine := range sshTunnelMachines {
global.Log.Debugf("开始定时检查ssh隧道机器[%d]是否还有被使用...", mid)
logx.Debugf("开始定时检查ssh隧道机器[%d]是否还有被使用...", mid)
hasUse := false
for _, checkUseFunc := range checkSshTunnelMachineHasUseFuncs {
// 如果一个在使用则返回不关闭,不继续后续检查
@@ -126,10 +126,10 @@ func (stm *SshTunnelMachine) Close() {
}
if stm.SshClient != nil {
global.Log.Infof("ssh隧道机器[%d]未被使用, 关闭隧道...", stm.machineId)
logx.Infof("ssh隧道机器[%d]未被使用, 关闭隧道...", stm.machineId)
err := stm.SshClient.Close()
if err != nil {
global.Log.Errorf("关闭ssh隧道机器[%d]发生错误: %s", stm.machineId, err.Error())
logx.Errorf("关闭ssh隧道机器[%d]发生错误: %s", stm.machineId, err.Error())
}
}
delete(sshTunnelMachines, stm.machineId)
@@ -152,7 +152,7 @@ func GetSshTunnelMachine(machineId int, getMachine func(uint64) *Info) (*SshTunn
}
sshTunnelMachine = &SshTunnelMachine{SshClient: sshClient, machineId: machineId, tunnels: map[uint64]*Tunnel{}}
global.Log.Infof("初次连接ssh隧道机器[%d][%s:%d]", machineId, me.Ip, me.Port)
logx.Infof("初次连接ssh隧道机器[%d][%s:%d]", machineId, me.Ip, me.Port)
sshTunnelMachines[machineId] = sshTunnelMachine
// 如果实用了隧道机器且还没开始定时检查是否还被实用,则执行定时任务检测隧道是否还被使用
@@ -195,31 +195,31 @@ func (r *Tunnel) Open(sshClient *ssh.Client) {
localAddr := fmt.Sprintf("%s:%d", r.localHost, r.localPort)
for {
global.Log.Debugf("隧道 %v 等待客户端访问 %v", r.id, localAddr)
logx.Debugf("隧道 %v 等待客户端访问 %v", r.id, localAddr)
localConn, err := r.listener.Accept()
if err != nil {
global.Log.Debugf("隧道 %v 接受连接失败 %v, 退出循环", r.id, err.Error())
global.Log.Debug("-------------------------------------------------")
logx.Debugf("隧道 %v 接受连接失败 %v, 退出循环", r.id, err.Error())
logx.Debug("-------------------------------------------------")
return
}
r.localConnections = append(r.localConnections, localConn)
global.Log.Debugf("隧道 %v 新增本地连接 %v", r.id, localConn.RemoteAddr().String())
logx.Debugf("隧道 %v 新增本地连接 %v", r.id, localConn.RemoteAddr().String())
remoteAddr := fmt.Sprintf("%s:%d", r.remoteHost, r.remotePort)
global.Log.Debugf("隧道 %v 连接远程地址 %v ...", r.id, remoteAddr)
logx.Debugf("隧道 %v 连接远程地址 %v ...", r.id, remoteAddr)
remoteConn, err := sshClient.Dial("tcp", remoteAddr)
if err != nil {
global.Log.Debugf("隧道 %v 连接远程地址 %v, 退出循环", r.id, err.Error())
global.Log.Debug("-------------------------------------------------")
logx.Debugf("隧道 %v 连接远程地址 %v, 退出循环", r.id, err.Error())
logx.Debug("-------------------------------------------------")
return
}
r.remoteConnections = append(r.remoteConnections, remoteConn)
global.Log.Debugf("隧道 %v 连接远程主机成功", r.id)
logx.Debugf("隧道 %v 连接远程主机成功", r.id)
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
global.Log.Debugf("隧道 %v 开始转发数据 [%v]->[%v]", r.id, localAddr, remoteAddr)
global.Log.Debug("~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~~")
logx.Debugf("隧道 %v 开始转发数据 [%v]->[%v]", r.id, localAddr, remoteAddr)
logx.Debug("~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~~")
}
}
@@ -233,7 +233,7 @@ func (r *Tunnel) Close() {
}
r.remoteConnections = nil
_ = r.listener.Close()
global.Log.Debugf("隧道 %d 监听器关闭", r.id)
logx.Debugf("隧道 %d 监听器关闭", r.id)
}
func copyConn(writer, reader net.Conn) {

View File

@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"io"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"time"
"unicode/utf8"
@@ -68,12 +68,12 @@ func (r TerminalSession) Start() {
}
func (r TerminalSession) Stop() {
global.Log.Debug("close machine ssh terminal session")
logx.Debug("close machine ssh terminal session")
r.tick.Stop()
r.cancel()
if r.terminal != nil {
if err := r.terminal.Close(); err != nil {
global.Log.Errorf("关闭机器ssh终端失败: %s", err.Error())
logx.Errorf("关闭机器ssh终端失败: %s", err.Error())
}
}
}
@@ -87,7 +87,7 @@ func (ts TerminalSession) readFormTerminal() {
rn, size, err := ts.terminal.ReadRune()
if err != nil {
if err != io.EOF {
global.Log.Error("机器ssh终端读取消息失败: ", err)
logx.Error("机器ssh终端读取消息失败: ", err)
}
return
}
@@ -108,7 +108,7 @@ func (ts TerminalSession) writeToWebsocket() {
if len(buf) > 0 {
s := string(buf)
if err := WriteMessage(ts.wsConn, s); err != nil {
global.Log.Error("机器ssh终端发送消息至websocket失败: ", err)
logx.Error("机器ssh终端发送消息至websocket失败: ", err)
return
}
// 如果记录器存在,则记录操作回放信息
@@ -148,25 +148,25 @@ func (ts *TerminalSession) receiveWsMsg() {
// read websocket msg
_, wsData, err := wsConn.ReadMessage()
if err != nil {
global.Log.Debug("机器ssh终端读取websocket消息失败: ", err)
logx.Debugf("机器ssh终端读取websocket消息失败: %s", err.Error())
return
}
// 解析消息
msgObj := WsMsg{}
if err := json.Unmarshal(wsData, &msgObj); err != nil {
global.Log.Error("机器ssh终端消息解析失败: ", err)
logx.Error("机器ssh终端消息解析失败: ", err)
}
switch msgObj.Type {
case Resize:
if msgObj.Cols > 0 && msgObj.Rows > 0 {
if err := ts.terminal.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
global.Log.Error("ssh pty change windows size failed")
logx.Error("ssh pty change windows size failed")
}
}
case Data:
_, err := ts.terminal.Write([]byte(msgObj.Msg))
if err != nil {
global.Log.Debugf("机器ssh终端写入消息失败: %s", err)
logx.Debugf("机器ssh终端写入消息失败: %s", err)
}
case Ping:
_, err := ts.terminal.SshSession.SendRequest("ping", true, nil)

View File

@@ -40,9 +40,9 @@ func (m *machineFileRepoImpl) Delete(id uint64) {
}
func (m *machineFileRepoImpl) Create(entity *entity.MachineFile) {
gormx.Insert(entity)
biz.ErrIsNil(gormx.Insert(entity), "新增机器文件配置失败")
}
func (m *machineFileRepoImpl) UpdateById(entity *entity.MachineFile) {
gormx.UpdateById(entity)
biz.ErrIsNil(gormx.UpdateById(entity), "更新机器文件失败")
}

View File

@@ -10,7 +10,7 @@ import (
"mayfly-go/internal/mongo/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/netx"
"mayfly-go/pkg/utils/structx"
@@ -104,7 +104,7 @@ func (d *mongoAppImpl) GetMongoInst(id uint64) *MongoInstance {
var mongoCliCache = cache.NewTimedCache(consts.MongoConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key any, value any) {
global.Log.Info("删除mongo连接缓存: id = ", key)
logx.Info("删除mongo连接缓存: id = ", key)
value.(*MongoInstance).Close()
})
@@ -162,7 +162,7 @@ type MongoInstance struct {
func (mi *MongoInstance) Close() {
if mi.Cli != nil {
if err := mi.Cli.Disconnect(context.Background()); err != nil {
global.Log.Errorf("关闭mongo实例[%d]连接失败: %s", mi.Id, err)
logx.Errorf("关闭mongo实例[%d]连接失败: %s", mi.Id, err)
}
mi.Cli = nil
}
@@ -192,7 +192,7 @@ func connect(me *entity.Mongo) (*MongoInstance, error) {
return nil, err
}
global.Log.Infof("连接mongo: %s", func(str string) string {
logx.Infof("连接mongo: %s", func(str string) string {
reg := regexp.MustCompile(`(^mongodb://.+?:)(.+)(@.+$)`)
return reg.ReplaceAllString(str, `${1}****${3}`)
}(me.Uri))

View File

@@ -10,7 +10,7 @@ import (
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/netx"
"mayfly-go/pkg/utils/structx"
@@ -163,7 +163,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64, db int) *RedisInstance {
}
}
global.Log.Infof("连接redis: %s", re.Host)
logx.Infof("连接redis: %s", re.Host)
if needCache {
redisCache.Put(getRedisCacheKey(id, db), ri)
}
@@ -257,7 +257,7 @@ func getRedisDialer(machineId int) func(ctx context.Context, network, addr strin
var redisCache = cache.NewTimedCache(consts.RedisConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key any, value any) {
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %s", key))
logx.Info(fmt.Sprintf("删除redis连接缓存 id = %s", key))
value.(*RedisInstance).Close()
})
@@ -350,13 +350,13 @@ func (r *RedisInstance) Close() {
mode := r.Info.Mode
if mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
if err := r.Cli.Close(); err != nil {
global.Log.Errorf("关闭redis单机实例[%s]连接失败: %s", r.Id, err.Error())
logx.Errorf("关闭redis单机实例[%s]连接失败: %s", r.Id, err.Error())
}
r.Cli = nil
}
if mode == entity.RedisModeCluster {
if err := r.ClusterCli.Close(); err != nil {
global.Log.Errorf("关闭redis集群实例[%s]连接失败: %s", r.Id, err.Error())
logx.Errorf("关闭redis集群实例[%s]连接失败: %s", r.Id, err.Error())
}
r.ClusterCli = nil
}

View File

@@ -2,7 +2,7 @@ package api
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/ws"
@@ -18,7 +18,7 @@ func (s *System) ConnectWs(g *gin.Context) {
wsConn, err := ws.Upgrader.Upgrade(g.Writer, g.Request, nil)
defer func() {
if err := recover(); err != nil {
global.Log.Error(err.(error).Error())
logx.ErrorTrace("websocket连接失败: ", err.(error))
if wsConn != nil {
wsConn.WriteMessage(websocket.TextMessage, []byte(err.(error).Error()))
wsConn.Close()

View File

@@ -6,7 +6,7 @@ import (
"mayfly-go/internal/sys/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/jsonx"
"strings"
@@ -61,7 +61,7 @@ func (a *configAppImpl) GetConfig(key string) *entity.Config {
}
if err := a.configRepo.GetConfig(config, "Id", "Key", "Value", "Permission"); err != nil {
global.Log.Warnf("不存在key = [%s] 的系统配置", key)
logx.Warnf("不存在key = [%s] 的系统配置", key)
} else {
cache.SetStr(SysConfigKeyPrefix+key, jsonx.ToStr(config), -1)
}

View File

@@ -2,7 +2,7 @@ package biz
import (
"fmt"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/anyx"
"reflect"
@@ -10,13 +10,14 @@ import (
func ErrIsNil(err error, msg string, params ...any) {
if err != nil {
global.Log.Error(msg + ": " + err.Error())
// logx.ErrorTrace(msg, err)
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func ErrIsNilAppendErr(err error, msg string) {
if err != nil {
// logx.ErrorTrace(msg, err)
panic(NewBizErr(fmt.Sprintf(msg, err.Error())))
}
}
@@ -26,7 +27,7 @@ func IsNil(err error) {
case *BizError:
panic(t)
case error:
global.Log.Error("非业务异常: " + err.Error())
logx.Error("非业务异常: " + err.Error())
panic(NewBizErr(fmt.Sprintf("非业务异常: %s", err.Error())))
}
}

View File

@@ -1,5 +1,7 @@
package biz
import "fmt"
// 业务错误
type BizError struct {
code int16
@@ -23,6 +25,10 @@ func (e BizError) Code() int16 {
return e.code
}
func (e BizError) String() string {
return fmt.Sprintf("errCode: %d, errMsg: %s", e.Code(), e.Error())
}
// 创建业务逻辑错误结构体,默认为业务逻辑错误
func NewBizErr(msg string) BizError {
return BizError{code: BizErr.code, err: msg}

View File

@@ -1,7 +1,7 @@
package cache
import (
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/rediscli"
"strconv"
"time"
@@ -33,7 +33,7 @@ func GetInt(key string) int {
return 0
}
if intV, err := strconv.Atoi(val); err != nil {
global.Log.Error("获取缓存中的int值转换失败", err)
logx.Error("获取缓存中的int值转换失败", err)
return 0
} else {
return intV

View File

@@ -21,6 +21,9 @@ func (a *Aes) DecryptBase64(data string) ([]byte, error) {
}
func (j *Aes) Valid() {
if j.Key == "" {
return
}
aesKeyLen := len(j.Key)
assert.IsTrue(aesKeyLen == 16 || aesKeyLen == 24 || aesKeyLen == 32,
fmt.Sprintf("config.yml之 [aes.key] 长度需为16、24、32位长度, 当前为%d位", aesKeyLen))

View File

@@ -3,14 +3,21 @@ package config
import (
"flag"
"fmt"
"log/slog"
"mayfly-go/pkg/utils/assert"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/ymlx"
"os"
"path/filepath"
"strconv"
)
type ConfigItem interface {
// 验证配置
Valid()
// 如果不存在配置值,则设置默认值
Default()
}
// 配置文件映射对象
var Conf *Config
@@ -23,14 +30,14 @@ func Init() {
// 读取配置文件信息
yc := &Config{}
if err := ymlx.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
slog.Warn(fmt.Sprintf("读取配置文件[%s]失败: %s, 使用系统默认配置或环境变量配置", startConfigParam.ConfigFilePath, err.Error()))
// 设置默认信息,主要方便后续的系统环境变量替换
yc.SetDefaultConfig()
logx.Warn(fmt.Sprintf("读取配置文件[%s]失败: %s, 使用系统默认配置或环境变量配置", startConfigParam.ConfigFilePath, err.Error()))
}
// 校验配置文件内容信息
yc.Valid()
// 尝试使用系统环境变量替换配置信息
yc.ReplaceOsEnv()
yc.IfBlankDefaultValue()
// 校验配置文件内容信息
yc.Valid()
Conf = yc
}
@@ -41,22 +48,35 @@ type CmdConfigParam struct {
// yaml配置文件映射对象
type Config struct {
Server *Server `yaml:"server"`
Jwt *Jwt `yaml:"jwt"`
Aes *Aes `yaml:"aes"`
Mysql *Mysql `yaml:"mysql"`
Redis *Redis `yaml:"redis"`
Log *Log `yaml:"log"`
Server Server `yaml:"server"`
Jwt Jwt `yaml:"jwt"`
Aes Aes `yaml:"aes"`
Mysql Mysql `yaml:"mysql"`
Redis Redis `yaml:"redis"`
Log Log `yaml:"log"`
}
func (c *Config) IfBlankDefaultValue() {
c.Log.Default()
// 优先初始化log因为后续的一些default方法中会需要用到。统一日志输出
logx.Init(logx.Config{
Level: c.Log.Level,
Type: c.Log.Type,
AddSource: c.Log.AddSource,
Filename: c.Log.File.Name,
Filepath: c.Log.File.Path,
})
c.Server.Default()
c.Jwt.Default()
c.Mysql.Default()
}
// 配置文件内容校验
func (c *Config) Valid() {
assert.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空")
c.Jwt.Valid()
if c.Aes != nil {
c.Aes.Valid()
}
}
// 替换系统环境变量,如果环境变量中存在该值,则优秀使用环境变量设定的值
func (c *Config) ReplaceOsEnv() {
@@ -99,29 +119,3 @@ func (c *Config) ReplaceOsEnv() {
c.Jwt.Key = jwtKey
}
}
func (c *Config) SetDefaultConfig() {
c.Server = &Server{
Model: "release",
Port: 8888,
MachineRecPath: "./rec",
}
c.Jwt = &Jwt{
ExpireTime: 1440,
}
c.Aes = &Aes{
Key: "1111111111111111",
}
c.Mysql = &Mysql{
Host: "localhost:3306",
Config: "charset=utf8&loc=Local&parseTime=true",
MaxIdleConns: 5,
}
c.Log = &Log{
Level: "info",
}
}

View File

@@ -1,12 +1,29 @@
package config
import "mayfly-go/pkg/utils/assert"
import (
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/assert"
"mayfly-go/pkg/utils/stringx"
)
type Jwt struct {
Key string `yaml:"key"`
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
}
func (j *Jwt) Default() {
if j.Key == "" {
// 如果配置文件中的jwt key为空则随机生成字符串
j.Key = stringx.Rand(32)
logx.Warnf("未配置jwt.key, 随机生成key为: %s", j.Key)
}
if j.ExpireTime == 0 {
j.ExpireTime = 1440
logx.Warnf("未配置jwt.expire-time, 默认值: %d", j.ExpireTime)
}
}
func (j *Jwt) Valid() {
assert.IsTrue(j.ExpireTime != 0, "config.yml之[jwt.expire-time] 不能为空")
}

View File

@@ -1,10 +1,28 @@
package config
import "path"
import (
"mayfly-go/pkg/logx"
"path"
)
type Log struct {
Level string `yaml:"level"`
File *LogFile `yaml:"file"`
Type string `yaml:"type"`
AddSource bool `yaml:"add-source"`
File LogFile `yaml:"file"`
}
func (l *Log) Default() {
if l.Level == "" {
l.Level = "info"
logx.Warnf("未配置log.level, 默认值: %s", l.Level)
}
if l.Type == "" {
l.Type = "text"
}
if l.File.Name == "" {
l.File.Name = "mayfly-go.log"
}
}
type LogFile struct {

View File

@@ -1,5 +1,7 @@
package config
import "mayfly-go/pkg/logx"
type Mysql struct {
AutoMigration bool `mapstructure:"auto-migration" json:"autoMigration" yaml:"auto-migration"`
Host string `mapstructure:"path" json:"host" yaml:"host"`
@@ -13,6 +15,19 @@ type Mysql struct {
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
}
func (m *Mysql) Default() {
if m.Host == "" {
m.Host = "localhost:3306"
logx.Warnf("未配置mysql.host, 默认值: %s", m.Host)
}
if m.Config == "" {
m.Config = "charset=utf8&loc=Local&parseTime=true"
}
if m.MaxIdleConns == 0 {
m.MaxIdleConns = 5
}
}
func (m *Mysql) Dsn() string {
return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
}

View File

@@ -1,6 +1,8 @@
package config
import "fmt"
import (
"fmt"
)
type Server struct {
Port int `yaml:"port"`
@@ -12,6 +14,18 @@ type Server struct {
MachineRecPath string `yaml:"machine-rec-path"` // 机器终端操作回放文件存储路径
}
func (s *Server) Default() {
if s.Model == "" {
s.Model = "release"
}
if s.Port == 0 {
s.Port = 8888
}
if s.MachineRecPath == "" {
s.MachineRecPath = "./rec"
}
}
func (s *Server) GetPort() string {
return fmt.Sprintf(":%d", s.Port)
}

View File

@@ -3,12 +3,11 @@ package ginx
import (
"io"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/structx"
"mayfly-go/pkg/validatorx"
"net/http"
"runtime/debug"
"strconv"
"github.com/gin-gonic/gin"
@@ -105,12 +104,10 @@ func ErrorRes(g *gin.Context, err any) {
g.JSON(http.StatusOK, model.Error(t))
case error:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Errorf("%s\n%s", t.Error(), string(debug.Stack()))
case string:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Errorf("%s\n%s", t, string(debug.Stack()))
default:
global.Log.Error(t)
logx.Error("未知错误", "errInfo", t)
}
}

View File

@@ -1,11 +1,9 @@
package global
import (
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
var (
Log *logrus.Logger // 日志
Db *gorm.DB // gorm
)

View File

@@ -1,65 +0,0 @@
package logger
import (
"fmt"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
)
func Init() {
logger := logrus.New()
logger.SetFormatter(new(LogFormatter))
logger.SetReportCaller(true)
logConf := config.Conf.Log
// 如果不存在日志配置信息则默认debug级别
if logConf == nil {
logger.SetLevel(logrus.DebugLevel)
return
}
// 根据配置文件设置日志级别
if level := logConf.Level; level != "" {
l, err := logrus.ParseLevel(level)
if err != nil {
panic(fmt.Sprintf("日志级别不存在: %s", level))
}
logger.SetLevel(l)
} else {
logger.SetLevel(logrus.DebugLevel)
}
if logFile := logConf.File; logFile != nil {
//写入文件
file, err := os.OpenFile(logFile.GetFilename(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
if err != nil {
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
}
logger.Out = file
}
global.Log = logger
}
type LogFormatter struct{}
func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000")
level := entry.Level
logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
// 如果存在调用信息,记录方法信息及行号
if caller := entry.Caller; caller != nil {
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.Function, caller.Line)
}
for k, v := range entry.Data {
logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)
}
logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message)
return []byte(logMsg), nil
}

16
server/pkg/logx/color.go Normal file
View File

@@ -0,0 +1,16 @@
package logx
const (
Reset = "\033[0m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Magenta = "\033[35m"
Cyan = "\033[36m"
White = "\033[37m"
BlueBold = "\033[34;1m"
MagentaBold = "\033[35;1m"
RedBold = "\033[31;1m"
YellowBold = "\033[33;1m"
)

63
server/pkg/logx/config.go Normal file
View File

@@ -0,0 +1,63 @@
package logx
import (
"fmt"
"io"
"log/slog"
"os"
"path"
"strings"
)
type Config struct {
Level string
Type string // 日志类型text、json
AddSource bool // 是否记录调用方法
Filename string
Filepath string
writer io.Writer
}
// 获取日志输出源
func (c *Config) GetLogOut() io.Writer {
if c.writer != nil {
return c.writer
}
writer := os.Stdout
// 根据配置文件设置日志级别
if c.Filepath != "" && c.Filename != "" {
// 写入文件
file, err := os.OpenFile(path.Join(c.Filepath, c.Filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
if err != nil {
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
}
writer = file
}
c.writer = writer
return writer
}
// 获取日志级别
func (c *Config) GetLevel() slog.Level {
switch strings.ToLower(c.Level) {
case "error":
return slog.LevelDebug
case "warn", "warning":
return slog.LevelWarn
case "info":
return slog.LevelInfo
case "debug":
return slog.LevelDebug
}
return slog.LevelInfo
}
func (c *Config) IsJsonType() bool {
return c.Type == "json"
}
func (c *Config) IsDebug() bool {
return strings.ToLower(c.Level) == "debug"
}

View File

@@ -0,0 +1,22 @@
package logx
import (
"log/slog"
"time"
)
func NewJsonHandler(config *Config) *slog.JSONHandler {
replace := func(groups []string, a slog.Attr) slog.Attr {
// 格式化时间.
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{Key: "time", Value: slog.StringValue(time.Now().Local().Format("2006-01-02 15:04:05.000"))}
}
return a
}
return slog.NewJSONHandler(config.GetLogOut(), &slog.HandlerOptions{
Level: config.GetLevel(),
AddSource: false, // 统一由添加公共commonAttrs时判断添加
ReplaceAttr: replace,
})
}

174
server/pkg/logx/logx.go Normal file
View File

@@ -0,0 +1,174 @@
package logx
import (
"context"
"fmt"
"log/slog"
"mayfly-go/pkg/utils/runtimex"
"runtime"
"strings"
)
var (
config *Config
)
func GetConfig() *Config {
if config == nil {
return &Config{
Level: "info",
Type: "text",
AddSource: false,
}
}
return config
}
func Init(logConf Config) {
config = &logConf
var handler slog.Handler
if logConf.Type == "text" {
handler = NewTextHandler(config)
} else {
handler = NewJsonHandler(config)
}
slog.SetDefault(slog.New(handler))
}
func Print(msg string, args ...any) {
Log(context.Background(), slog.LevelInfo, msg, args...)
}
func Debug(msg string, args ...any) {
Log(context.Background(), slog.LevelDebug, msg, args...)
}
func Debugf(format string, args ...any) {
Log(context.Background(), slog.LevelDebug, fmt.Sprintf(format, args...))
}
func DebugWithFields(msg string, mapFields map[string]any) {
Log(context.Background(), slog.LevelDebug, msg, map2Attrs(mapFields)...)
}
// debug记录并将堆栈信息添加至msg里默认记录10个堆栈信息
func DebugTrace(msg string, err error) {
Log(context.Background(), slog.LevelDebug, fmt.Sprintf(msg+"%s\n%s", err.Error(), runtimex.StatckStr(2, 10)))
}
func Info(msg string, args ...any) {
Log(context.Background(), slog.LevelInfo, msg, args...)
}
func InfoContext(ctx context.Context, msg string, args ...any) {
Log(ctx, slog.LevelInfo, msg, args...)
}
func Infof(format string, args ...any) {
Log(context.Background(), slog.LevelInfo, fmt.Sprintf(format, args...))
}
func InfoWithFields(msg string, mapFields map[string]any) {
Log(context.Background(), slog.LevelInfo, msg, map2Attrs(mapFields)...)
}
func Warn(msg string, args ...any) {
Log(context.Background(), slog.LevelWarn, msg, args...)
}
func Warnf(format string, args ...any) {
Log(context.Background(), slog.LevelWarn, fmt.Sprintf(format, args...))
}
func WarnWithFields(msg string, mapFields map[string]any) {
Log(context.Background(), slog.LevelWarn, msg, map2Attrs(mapFields)...)
}
func Error(msg string, args ...any) {
Log(context.Background(), slog.LevelError, msg, args...)
}
func Errorf(format string, args ...any) {
Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
}
// 错误记录并将堆栈信息添加至msg里默认记录10个堆栈信息
func ErrorTrace(msg string, err error) {
Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+"%s\n%s", err.Error(), runtimex.StatckStr(2, 10)))
}
func ErrorWithFields(msg string, mapFields map[string]any) {
Log(context.Background(), slog.LevelError, msg, map2Attrs(mapFields)...)
}
func Panic(msg string, args ...any) {
Log(context.Background(), slog.LevelError, msg, args...)
panic(msg)
}
func Panicf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
panic(msg)
}
func Log(ctx context.Context, level slog.Level, msg string, args ...any) {
slog.Log(ctx, level, msg, appendCommonAttr(ctx, level, args)...)
}
// 获取日志公共属性
func getCommonAttr(ctx context.Context, level slog.Level) []any {
commonAttrs := make([]any, 0)
// 如果系统配置添加方法信息或者为错误级别时则 记录方法信息及行号
if config.AddSource || level == slog.LevelError {
// skip [runtime.Callers, getCommonAttr, appendCommonAttr, logx.Log, logx.Info|Debug|Warn|Error..]
var pcs [1]uintptr
runtime.Callers(5, pcs[:])
fs := runtime.CallersFrames(pcs[:])
f, _ := fs.Next()
source := &Source{
Function: f.Function,
Fileline: fmt.Sprintf("%s:%d", runtimex.ParseFrameFile(f.File), f.Line),
}
commonAttrs = append(commonAttrs, slog.SourceKey, source)
}
return commonAttrs
}
func appendCommonAttr(ctx context.Context, level slog.Level, args []any) []any {
commonAttrs := getCommonAttr(ctx, level)
if len(commonAttrs) > 0 {
args = append(commonAttrs, args...)
}
return args
}
// map类型转为attr
func map2Attrs(mapArg map[string]any) []any {
atts := make([]any, 0)
for k, v := range mapArg {
atts = append(atts, slog.Any(k, v))
}
return atts
}
type Source struct {
// Function is the package path-qualified function name containing the
// source line. If non-empty, this string uniquely identifies a single
// function in the program. This may be the empty string if not known.
Function string `json:"function"`
// File and Line are the file name and line number (1-based) of the source
// line. These may be the empty string and zero, respectively, if not known.
Fileline string `json:"fileline"`
}
func (s Source) String() string {
// 查找最后一个/的位置, 如mayfly-go/pkg/starter.runWebServer
lastIndex := strings.LastIndex(s.Function, "/")
// 获取最后一段,即starter.runWebServer
funcName := s.Function[lastIndex+1:]
return fmt.Sprintf("%s#%s", s.Fileline, funcName)
}

View File

@@ -0,0 +1,54 @@
package logx
import (
"context"
"fmt"
"log"
"log/slog"
)
type CustomeTextHandlerOptions struct {
SlogOpts slog.HandlerOptions
}
type CustomeTextHandler struct {
slog.Handler
l *log.Logger
}
func (h *CustomeTextHandler) Handle(ctx context.Context, r slog.Record) error {
level := r.Level.String()
timeStr := r.Time.Format("2006-01-02 15:04:05.000")
attrsStr := ""
r.Attrs(func(a slog.Attr) bool {
// 如果是source则忽略key简洁些
if a.Key == slog.SourceKey {
attrsStr += fmt.Sprintf("[%s]", a.Value.Any())
return true
}
attrsStr += fmt.Sprintf("[%s=%v]", a.Key, a.Value.Any())
return true
})
if attrsStr != "" {
attrsStr = " " + attrsStr
}
// 格式为time [level] [key=value][key2=value2] : message
h.l.Printf("%s [%s]%s : %s", timeStr, level, attrsStr, r.Message)
return nil
}
func NewTextHandler(config *Config) *CustomeTextHandler {
opts := CustomeTextHandlerOptions{
SlogOpts: slog.HandlerOptions{
Level: config.GetLevel(),
AddSource: false, // 统一由添加公共commonAttrs时判断添加
}}
out := config.GetLogOut()
return &CustomeTextHandler{
Handler: slog.NewTextHandler(out, &opts.SlogOpts),
l: log.New(out, "", 0),
}
}

View File

@@ -2,7 +2,7 @@ package rediscli
import (
"context"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/stringx"
"time"
@@ -33,7 +33,7 @@ func NewLock(key string, expiration time.Duration) *RedisLock {
func (rl *RedisLock) Lock() bool {
result, err := cli.SetNX(context.Background(), LockKeyPrefix+rl.key, rl.value, rl.expiration).Result()
if err != nil {
global.Log.Errorf("redis lock setNx fail: %s", err.Error())
logx.Errorf("redis lock setNx fail: %s", err.Error())
return false
}
@@ -65,7 +65,7 @@ func (rl *RedisLock) UnLock() bool {
result, err := script.Run(context.Background(), cli, []string{LockKeyPrefix + rl.key}, rl.value).Int64()
if err != nil {
global.Log.Errorf("redis unlock runScript fail: %s", err.Error())
logx.Errorf("redis unlock runScript fail: %s", err.Error())
return false
}
@@ -88,7 +88,7 @@ func (rl *RedisLock) RefreshLock() bool {
result, err := script.Run(context.Background(), cli, []string{LockKeyPrefix + rl.key}, rl.value, rl.expiration/time.Second).Int64()
if err != nil {
global.Log.Errorf("redis refreshLock runScript fail: %s", err.Error())
logx.Errorf("redis refreshLock runScript fail: %s", err.Error())
return false
}

View File

@@ -3,11 +3,10 @@ package req
import (
"fmt"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/runtimex"
"mayfly-go/pkg/utils/stringx"
"github.com/sirupsen/logrus"
)
type SaveLogFunc func(*Ctx)
@@ -42,6 +41,8 @@ func (i *LogInfo) WithLogResp() *LogInfo {
return i
}
const DefaultLogFrames = 10
func LogHandler(rc *Ctx) error {
if rc.Conf == nil || rc.Conf.logInfo == nil {
return nil
@@ -49,24 +50,54 @@ func LogHandler(rc *Ctx) error {
li := rc.Conf.logInfo
lfs := logrus.Fields{}
if la := rc.LoginAccount; la != nil {
lfs["uid"] = la.Id
lfs["uname"] = la.Username
}
attrMap := make(map[string]any, 0)
req := rc.GinCtx.Request
lfs[req.Method] = req.URL.Path
attrMap[req.Method] = req.URL.Path
if la := rc.LoginAccount; la != nil {
attrMap["uid"] = la.Id
attrMap["uname"] = la.Username
}
// 如果需要保存日志,并且保存日志处理函数存在则执行保存日志函数
if li.save && saveLog != nil {
go saveLog(rc)
}
logMsg := li.Description
if logx.GetConfig().IsJsonType() {
// json格式日志处理
attrMap["req"] = rc.ReqParam
if li.LogResp {
attrMap["resp"] = rc.ResData
}
attrMap["exeTime"] = rc.timed
if rc.Err != nil {
nFrames := DefaultLogFrames
if _, ok := rc.Err.(biz.BizError); ok {
nFrames = nFrames / 2
}
attrMap["error"] = rc.Err
// 跳过log_handler等相关堆栈
attrMap["stacktrace"] = runtimex.StatckStr(5, nFrames)
}
} else {
// 处理文本格式日志信息
if err := rc.Err; err != nil {
global.Log.WithFields(lfs).Error(getErrMsg(rc, err))
logMsg = getErrMsg(rc, err)
} else {
logMsg = getLogMsg(rc)
}
}
if err := rc.Err; err != nil {
logx.ErrorWithFields(logMsg, attrMap)
return nil
}
global.Log.WithFields(lfs).Info(getLogMsg(rc))
logx.InfoWithFields(logMsg, attrMap)
return nil
}
@@ -85,19 +116,23 @@ func getLogMsg(rc *Ctx) string {
}
func getErrMsg(rc *Ctx, err any) string {
msg := rc.Conf.logInfo.Description
msg := rc.Conf.logInfo.Description + fmt.Sprintf(" ->%dms", rc.timed)
if !anyx.IsBlank(rc.ReqParam) {
msg = msg + fmt.Sprintf("\n--> %s", stringx.AnyToStr(rc.ReqParam))
}
nFrames := DefaultLogFrames
var errMsg string
switch t := err.(type) {
case biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
errMsg = fmt.Sprintf("\n<-e %s", t.String())
nFrames = nFrames / 2
case error:
errMsg = fmt.Sprintf("\n<-e errMsg: %s", t.Error())
case string:
errMsg = fmt.Sprintf("\n<-e errMsg: %s", t)
}
// 加上堆栈信息
errMsg += fmt.Sprintf("\n<-stacktrace: %s", runtimex.StatckStr(5, nFrames))
return (msg + errMsg)
}

View File

@@ -26,7 +26,9 @@ type Ctx struct {
func (rc *Ctx) Handle(handler HandlerFunc) {
ginCtx := rc.GinCtx
begin := time.Now()
defer func() {
rc.timed = time.Since(begin).Milliseconds()
if err := recover(); err != nil {
rc.Err = err
ginx.ErrorRes(ginCtx, err)
@@ -47,7 +49,6 @@ func (rc *Ctx) Handle(handler HandlerFunc) {
panic(err)
}
begin := time.Now()
handler(rc)
rc.timed = time.Since(begin).Milliseconds()
if rc.Conf == nil || !rc.Conf.noRes {

View File

@@ -5,47 +5,24 @@ import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/stringx"
"time"
"github.com/golang-jwt/jwt/v5"
)
// 初始化jwt key与expire time等
func InitTokenConfig() {
if ExpTime == 0 {
JwtKey = config.Conf.Jwt.Key
ExpTime = config.Conf.Jwt.ExpireTime
// 如果配置文件中的jwt key为空则随机生成字符串
if JwtKey == "" {
JwtKey = stringx.Rand(32)
global.Log.Infof("config.yml未配置jwt.key, 随机生成key为: %s", JwtKey)
}
}
}
var (
JwtKey string
ExpTime uint64
)
// 创建用户token
func CreateToken(userId uint64, username string) string {
InitTokenConfig()
// 带权限创建令牌
// 设置有效期过期需要重新登录获取token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": userId,
"username": username,
"exp": time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
"exp": time.Now().Add(time.Minute * time.Duration(config.Conf.Jwt.ExpireTime)).Unix(),
})
// 使用自定义字符串加密 and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(JwtKey))
tokenString, err := token.SignedString([]byte(config.Conf.Jwt.Key))
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
return tokenString
}
@@ -55,11 +32,10 @@ func ParseToken(tokenStr string) (*model.LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
InitTokenConfig()
// Parse token
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
return []byte(JwtKey), nil
return []byte(config.Conf.Jwt.Key), nil
})
if err != nil || token == nil {
return nil, err

View File

@@ -2,7 +2,7 @@ package scheduler
import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"sync"
"github.com/robfig/cron/v3"
@@ -32,7 +32,7 @@ func Remove(id cron.EntryID) {
// 根据任务key移除
func RemoveByKey(key string) {
global.Log.Debugf("移除cron任务 => [key = %s]", key)
logx.Debugf("移除cron任务 => [key = %s]", key)
id, ok := key2IdMap.Load(key)
if ok {
Remove(id.(cron.EntryID))
@@ -53,7 +53,7 @@ func AddFun(spec string, cmd func()) cron.EntryID {
// 根据key添加定时任务
func AddFunByKey(key, spec string, cmd func()) {
global.Log.Debugf("添加cron任务 => [key = %s]", key)
logx.Debugf("添加cron任务 => [key = %s]", key)
RemoveByKey(key)
key2IdMap.Store(key, AddFun(spec, cmd))
}

View File

@@ -3,15 +3,18 @@ package starter
import (
"fmt"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"os"
"runtime/debug"
)
func printBanner() {
global.Log.Print(fmt.Sprintf(`
buildInfo, _ := debug.ReadBuildInfo()
logx.Print(fmt.Sprintf(`
__ _
_ __ ___ __ _ _ _ / _| |_ _ __ _ ___
| '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) | version: %s
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) | version: %s | go_version: %s | pid: %d
|_| |_| |_|\__,_|\__, |_| |_|\__, | \__, |\___/
|___/ |___/ |___/ `, config.Version))
|___/ |___/ |___/ `, config.Version, buildInfo.GoVersion, os.Getpid()))
}

View File

@@ -4,7 +4,7 @@ import (
"log"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"os"
"mayfly-go/pkg/logx"
"time"
"gorm.io/driver/mysql"
@@ -19,11 +19,11 @@ func initDb() {
func gormMysql() *gorm.DB {
m := config.Conf.Mysql
if m == nil || m.Dbname == "" {
global.Log.Panic("未找到数据库配置信息")
if m.Dbname == "" {
logx.Panic("未找到数据库配置信息")
return nil
}
global.Log.Infof("连接mysql [%s]", m.Host)
logx.Infof("连接mysql [%s]", m.Host)
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
@@ -34,18 +34,19 @@ func gormMysql() *gorm.DB {
}
sqlLogLevel := logger.Error
logConf := config.Conf.Log
logConf := logx.GetConfig()
// 如果为配置文件中配置的系统日志级别为debug则打印gorm执行的sql信息
if logConf.Level == "debug" {
if logConf.IsDebug() {
sqlLogLevel = logger.Info
}
gormLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer日志输出的目标前缀和日志包含的内容——译者注
log.New(logConf.GetLogOut(), "\r\n", log.LstdFlags), // io writer日志输出的目标前缀和日志包含的内容——译者注
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: sqlLogLevel, // 日志级别, 改为logger.Info即可显示sql语句
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound记录未找到错误
Colorful: true, // 禁用彩色打印
Colorful: false, // 禁用彩色打印
},
)
@@ -55,7 +56,7 @@ func gormMysql() *gorm.DB {
}, Logger: gormLogger}
if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
global.Log.Panicf("连接mysql失败! [%s]", err.Error())
logx.Panicf("连接mysql失败! [%s]", err.Error())
return nil
} else {
sqlDB, _ := db.DB()

View File

@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/rediscli"
"github.com/redis/go-redis/v9"
@@ -17,11 +17,11 @@ func initRedis() {
func connRedis() *redis.Client {
// 设置redis客户端
redisConf := config.Conf.Redis
if redisConf == nil {
// global.Log.Panic("未找到redis配置信息")
if redisConf.Host == "" {
// logx.Panic("未找到redis配置信息")
return nil
}
global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)
logx.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", redisConf.Host, redisConf.Port),
Password: redisConf.Password, // no password set
@@ -30,7 +30,7 @@ func connRedis() *redis.Client {
// 测试连接
_, e := rdb.Ping(context.TODO()).Result()
if e != nil {
global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d][%s]", redisConf.Host, redisConf.Port, e.Error()))
logx.Panicf("连接redis失败! [%s:%d][%s]", redisConf.Host, redisConf.Port, e.Error())
}
return rdb
}

View File

@@ -5,17 +5,14 @@ import (
"mayfly-go/migrations"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logger"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/validatorx"
)
func RunWebServer() {
// 初始化config.yml配置文件映射信息
// 初始化config.yml配置文件映射信息或使用环境变量。并初始化系统日志相关配置
config.Init()
// 初始化日志配置信息
logger.Init()
// 打印banner
printBanner()
@@ -27,7 +24,7 @@ func RunWebServer() {
// 数据库升级操作
if err := migrations.RunMigrations(global.Db); err != nil {
global.Log.Fatalf("数据库升级失败: %v", err)
logx.Panicf("数据库升级失败: %v", err)
}
// 参数校验器初始化、如错误提示中文转译等

View File

@@ -4,11 +4,18 @@ import (
"mayfly-go/initialize"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
func runWebServer() {
// 设置gin日志输出器
logOut := logx.GetConfig().GetLogOut()
gin.DefaultErrorWriter = logOut
gin.DefaultWriter = logOut
// 权限处理器
req.UseBeforeHandlerInterceptor(req.PermissionHandler)
// 日志处理器
@@ -21,7 +28,7 @@ func runWebServer() {
server := config.Conf.Server
port := server.GetPort()
global.Log.Infof("Listening and serving HTTP on %s", port)
logx.Infof("Listening and serving HTTP on %s", port)
var err error
if server.Tls != nil && server.Tls.Enable {

View File

@@ -2,7 +2,7 @@ package jsonx
import (
"encoding/json"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"strings"
"github.com/buger/jsonparser"
@@ -18,7 +18,7 @@ func ToMapByBytes(bytes []byte) map[string]any {
var res map[string]any
err := json.Unmarshal(bytes, &res)
if err != nil {
global.Log.Errorf("json字符串转map失败: %s", err.Error())
logx.Errorf("json字符串转map失败: %s", err.Error())
}
return res
}
@@ -26,7 +26,7 @@ func ToMapByBytes(bytes []byte) map[string]any {
// 转换为json字符串
func ToStr(val any) string {
if strBytes, err := json.Marshal(val); err != nil {
global.Log.Error("toJsonStr error: ", err)
logx.ErrorTrace("toJsonStr error: ", err)
return ""
} else {
return string(strBytes)

View File

@@ -1,7 +1,7 @@
package netx
import (
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"net"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
@@ -43,7 +43,7 @@ func Ip2Region(ip string) string {
vIndex, err := xdb.LoadVectorIndexFromFile(ip2RegionDbPath)
// 第一次加载失败则默认调整为不使用ip2Region
if err != nil {
global.Log.Errorf("failed to load ip2region vector index from `%s`: %s\n", ip2RegionDbPath, err)
logx.Errorf("failed to load ip2region vector index from `%s`: %s\n", ip2RegionDbPath, err)
useIp2Region = false
return ""
}
@@ -54,7 +54,7 @@ func Ip2Region(ip string) string {
// 2、用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
searcher, err := xdb.NewWithVectorIndex(ip2RegionDbPath, vectorIndex)
if err != nil {
global.Log.Errorf("failed to create searcher with vector index: %s\n", err)
logx.Errorf("failed to create searcher with vector index: %s\n", err)
return ""
}
@@ -63,7 +63,7 @@ func Ip2Region(ip string) string {
// do the search
region, err := searcher.SearchByStr(ip)
if err != nil {
global.Log.Warnf("failed to SearchIP(%s): %s\n", ip, err)
logx.Warnf("failed to SearchIP(%s): %s\n", ip, err)
return ""
}
return region

View File

@@ -0,0 +1,44 @@
package runtimex
import (
"fmt"
"runtime"
"strings"
)
// 获取指定堆栈描述信息
//
// @param skip: 跳过堆栈个数
// @param nFrames: 需要描述的堆栈个数
func StatckStr(skip, nFrames int) string {
pcs := make([]uintptr, nFrames+1)
n := runtime.Callers(skip+1, pcs)
if n == 0 {
return "(no stack)"
}
frames := runtime.CallersFrames(pcs[:n])
var b strings.Builder
i := 0
for {
frame, more := frames.Next()
fmt.Fprintf(&b, "called from %s (%s:%d)\n\t", frame.Function, ParseFrameFile(frame.File), frame.Line)
if !more {
break
}
i++
if i >= nFrames {
fmt.Fprintf(&b, "(rest of stack elided...)\n")
break
}
}
return b.String()
}
// 处理栈帧文件名
func ParseFrameFile(frameFile string) string {
// 尝试将完整路径如/usr/local/.../mayfly-go/server/pkg/starter/web-server.go切割为pkg/starter/web-server.go
if ss := strings.Split(frameFile, "mayfly-go/server/"); len(ss) > 1 {
return ss[1]
}
return frameFile
}

View File

@@ -1,7 +1,7 @@
package validatorx
import (
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"regexp"
"github.com/go-playground/validator/v10"
@@ -33,7 +33,7 @@ func RegisterPattern(patternName string, regexpStr string, errMsg string) {
func patternValidFunc(f validator.FieldLevel) bool {
reg := regexpMap[f.Param()]
if reg == nil {
global.Log.Warnf("%s的正则校验规则不存在!", f.Param())
logx.Warnf("%s的正则校验规则不存在!", f.Param())
return false
}

View File

@@ -2,7 +2,7 @@ package ws
import (
"encoding/json"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"net/http"
"time"
@@ -56,7 +56,7 @@ func checkConn() {
// 删除ws连接
func Delete(userid uint64) {
global.Log.Debug("移除websocket连接uid = ", userid)
logx.Debugf("移除websocket连接: uid = %d", userid)
conn := conns[userid]
if conn != nil {
conn.Close()