diff --git a/server/config.yml.example b/server/config.yml.example index 7128c345..929884d5 100644 --- a/server/config.yml.example +++ b/server/config.yml.example @@ -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 \ No newline at end of file diff --git a/server/go.mod b/server/go.mod index 1e6d8dc5..02f67962 100644 --- a/server/go.mod +++ b/server/go.mod @@ -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 diff --git a/server/internal/common/utils/pwd.go b/server/internal/common/utils/pwd.go index 57d49286..fd0289f9 100644 --- a/server/internal/common/utils/pwd.go +++ b/server/internal/common/utils/pwd.go @@ -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) diff --git a/server/internal/db/application/db.go b/server/internal/db/application/db.go index f6efd3bd..0a091540 100644 --- a/server/internal/db/application/db.go +++ b/server/internal/db/application/db.go @@ -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() }) diff --git a/server/internal/db/application/db_sql_exec.go b/server/internal/db/application/db_sql_exec.go index 4ddb98cd..80efb3f3 100644 --- a/server/internal/db/application/db_sql_exec.go +++ b/server/internal/db/application/db_sql_exec.go @@ -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 { diff --git a/server/internal/machine/application/machine_cronjob.go b/server/internal/machine/application/machine_cronjob.go index 37390a49..3e83728a 100644 --- a/server/internal/machine/application/machine_cronjob.go +++ b/server/internal/machine/application/machine_cronjob.go @@ -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 || diff --git a/server/internal/machine/application/machine_file.go b/server/internal/machine/application/machine_file.go index eaafa78a..aa1a8b68 100644 --- a/server/internal/machine/application/machine_file.go +++ b/server/internal/machine/application/machine_file.go @@ -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) } } diff --git a/server/internal/machine/infrastructure/machine/machine.go b/server/internal/machine/infrastructure/machine/machine.go index 4e0f03a2..4b6a4359 100644 --- a/server/internal/machine/infrastructure/machine/machine.go +++ b/server/internal/machine/infrastructure/machine/machine.go @@ -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,25 +173,24 @@ func DeleteCli(id uint64) { // 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建 func GetCli(machineId uint64, getMachine func(uint64) *Info) (*Cli, error) { - cli, err := cliCache.ComputeIfAbsent(machineId, func(_ any) (any, error) { - me := getMachine(machineId) - err := IfUseSshTunnelChangeIpPort(me, getMachine) - if err != nil { - return nil, fmt.Errorf("ssh隧道连接失败: %s", err.Error()) - } - c, err := newClient(me) - if err != nil { - CloseSshTunnelMachine(me.SshTunnelMachineId, me.Id) - return nil, err - } - c.sshTunnelMachineId = me.SshTunnelMachineId - return c, nil - }) - - if cli != nil { - return cli.(*Cli), err + if load, ok := cliCache.Get(machineId); ok { + return load.(*Cli), nil } - return nil, err + + me := getMachine(machineId) + err := IfUseSshTunnelChangeIpPort(me, getMachine) + if err != nil { + return nil, fmt.Errorf("ssh隧道连接失败: %s", err.Error()) + } + c, err := newClient(me) + if err != nil { + CloseSshTunnelMachine(me.SshTunnelMachineId, me.Id) + return nil, err + } + c.sshTunnelMachineId = me.SshTunnelMachineId + + 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() diff --git a/server/internal/machine/infrastructure/machine/sshtunnel.go b/server/internal/machine/infrastructure/machine/sshtunnel.go index 17a9cacc..e5e445e7 100644 --- a/server/internal/machine/infrastructure/machine/sshtunnel.go +++ b/server/internal/machine/infrastructure/machine/sshtunnel.go @@ -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) { diff --git a/server/internal/machine/infrastructure/machine/terminal_session.go b/server/internal/machine/infrastructure/machine/terminal_session.go index d6f53faa..b8319df9 100644 --- a/server/internal/machine/infrastructure/machine/terminal_session.go +++ b/server/internal/machine/infrastructure/machine/terminal_session.go @@ -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) diff --git a/server/internal/machine/infrastructure/persistence/machine_file.go b/server/internal/machine/infrastructure/persistence/machine_file.go index 1b948c69..6525a1f4 100644 --- a/server/internal/machine/infrastructure/persistence/machine_file.go +++ b/server/internal/machine/infrastructure/persistence/machine_file.go @@ -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), "更新机器文件失败") } diff --git a/server/internal/mongo/application/mongo.go b/server/internal/mongo/application/mongo.go index 6578728a..5cb02f8d 100644 --- a/server/internal/mongo/application/mongo.go +++ b/server/internal/mongo/application/mongo.go @@ -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)) diff --git a/server/internal/redis/application/redis.go b/server/internal/redis/application/redis.go index d8291ac4..794cce64 100644 --- a/server/internal/redis/application/redis.go +++ b/server/internal/redis/application/redis.go @@ -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 } diff --git a/server/internal/sys/api/system.go b/server/internal/sys/api/system.go index ce721c01..c27d708c 100644 --- a/server/internal/sys/api/system.go +++ b/server/internal/sys/api/system.go @@ -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() diff --git a/server/internal/sys/application/config.go b/server/internal/sys/application/config.go index 58495765..1f600d50 100644 --- a/server/internal/sys/application/config.go +++ b/server/internal/sys/application/config.go @@ -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) } diff --git a/server/pkg/biz/assert.go b/server/pkg/biz/assert.go index 29472ccf..12ddf1c8 100644 --- a/server/pkg/biz/assert.go +++ b/server/pkg/biz/assert.go @@ -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()))) } } diff --git a/server/pkg/biz/bizerror.go b/server/pkg/biz/bizerror.go index ee916ef2..e7d34853 100644 --- a/server/pkg/biz/bizerror.go +++ b/server/pkg/biz/bizerror.go @@ -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} diff --git a/server/pkg/cache/str_cache.go b/server/pkg/cache/str_cache.go index 0bc829f2..0a687132 100644 --- a/server/pkg/cache/str_cache.go +++ b/server/pkg/cache/str_cache.go @@ -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 diff --git a/server/pkg/config/aes.go b/server/pkg/config/aes.go index ba8b9694..6f718ea0 100644 --- a/server/pkg/config/aes.go +++ b/server/pkg/config/aes.go @@ -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)) diff --git a/server/pkg/config/config.go b/server/pkg/config/config.go index 12b0b3b1..809b76f0 100644 --- a/server/pkg/config/config.go +++ b/server/pkg/config/config.go @@ -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,21 +48,34 @@ 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() - } + c.Aes.Valid() } // 替换系统环境变量,如果环境变量中存在该值,则优秀使用环境变量设定的值 @@ -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", - } -} diff --git a/server/pkg/config/jwt.go b/server/pkg/config/jwt.go index 24c640e7..e9bb11a2 100644 --- a/server/pkg/config/jwt.go +++ b/server/pkg/config/jwt.go @@ -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) Valid() { - assert.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.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] 不能为空") } diff --git a/server/pkg/config/log.go b/server/pkg/config/log.go index ac9815be..5d69f858 100644 --- a/server/pkg/config/log.go +++ b/server/pkg/config/log.go @@ -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"` + Level string `yaml:"level"` + 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 { diff --git a/server/pkg/config/mysql.go b/server/pkg/config/mysql.go index b761b167..491e4ba7 100644 --- a/server/pkg/config/mysql.go +++ b/server/pkg/config/mysql.go @@ -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 } diff --git a/server/pkg/config/server.go b/server/pkg/config/server.go index 4291a713..7a3b9203 100644 --- a/server/pkg/config/server.go +++ b/server/pkg/config/server.go @@ -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) } diff --git a/server/pkg/ginx/ginx.go b/server/pkg/ginx/ginx.go index e15dfa31..fc2f707e 100644 --- a/server/pkg/ginx/ginx.go +++ b/server/pkg/ginx/ginx.go @@ -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) } } diff --git a/server/pkg/global/global.go b/server/pkg/global/global.go index 3eb6cf3e..3734edbd 100644 --- a/server/pkg/global/global.go +++ b/server/pkg/global/global.go @@ -1,11 +1,9 @@ package global import ( - "github.com/sirupsen/logrus" "gorm.io/gorm" ) var ( - Log *logrus.Logger // 日志 - Db *gorm.DB // gorm + Db *gorm.DB // gorm ) diff --git a/server/pkg/logger/logger.go b/server/pkg/logger/logger.go deleted file mode 100644 index d4c07841..00000000 --- a/server/pkg/logger/logger.go +++ /dev/null @@ -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 -} diff --git a/server/pkg/logx/color.go b/server/pkg/logx/color.go new file mode 100644 index 00000000..26350087 --- /dev/null +++ b/server/pkg/logx/color.go @@ -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" +) diff --git a/server/pkg/logx/config.go b/server/pkg/logx/config.go new file mode 100644 index 00000000..3e4bea50 --- /dev/null +++ b/server/pkg/logx/config.go @@ -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" +} diff --git a/server/pkg/logx/json_handler.go b/server/pkg/logx/json_handler.go new file mode 100644 index 00000000..2e26347b --- /dev/null +++ b/server/pkg/logx/json_handler.go @@ -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, + }) +} diff --git a/server/pkg/logx/logx.go b/server/pkg/logx/logx.go new file mode 100644 index 00000000..5ef67aff --- /dev/null +++ b/server/pkg/logx/logx.go @@ -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) +} diff --git a/server/pkg/logx/text_handler.go b/server/pkg/logx/text_handler.go new file mode 100644 index 00000000..8f52e8f9 --- /dev/null +++ b/server/pkg/logx/text_handler.go @@ -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), + } +} diff --git a/server/pkg/rediscli/lock.go b/server/pkg/rediscli/lock.go index 604692ac..cfe65314 100644 --- a/server/pkg/rediscli/lock.go +++ b/server/pkg/rediscli/lock.go @@ -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 } diff --git a/server/pkg/req/log_handler.go b/server/pkg/req/log_handler.go index d54f34d7..f9f5824e 100644 --- a/server/pkg/req/log_handler.go +++ b/server/pkg/req/log_handler.go @@ -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 { + logMsg = getErrMsg(rc, err) + } else { + logMsg = getLogMsg(rc) + } + } + if err := rc.Err; err != nil { - global.Log.WithFields(lfs).Error(getErrMsg(rc, err)) + 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) } diff --git a/server/pkg/req/req_ctx.go b/server/pkg/req/req_ctx.go index e5c23f30..a4c6c9b9 100644 --- a/server/pkg/req/req_ctx.go +++ b/server/pkg/req/req_ctx.go @@ -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 { diff --git a/server/pkg/req/token.go b/server/pkg/req/token.go index 2529f443..e847c54c 100644 --- a/server/pkg/req/token.go +++ b/server/pkg/req/token.go @@ -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 diff --git a/server/pkg/scheduler/scheduler.go b/server/pkg/scheduler/scheduler.go index d41b2cad..fd30dd48 100644 --- a/server/pkg/scheduler/scheduler.go +++ b/server/pkg/scheduler/scheduler.go @@ -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)) } diff --git a/server/pkg/starter/banner.go b/server/pkg/starter/banner.go index 14839bfa..45bf3cf5 100644 --- a/server/pkg/starter/banner.go +++ b/server/pkg/starter/banner.go @@ -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())) } diff --git a/server/pkg/starter/gorm.go b/server/pkg/starter/gorm.go index c460189e..99e1cc31 100644 --- a/server/pkg/starter/gorm.go +++ b/server/pkg/starter/gorm.go @@ -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() diff --git a/server/pkg/starter/redis.go b/server/pkg/starter/redis.go index 2cb200c8..1888b4e3 100644 --- a/server/pkg/starter/redis.go +++ b/server/pkg/starter/redis.go @@ -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 } diff --git a/server/pkg/starter/run.go b/server/pkg/starter/run.go index 549460bc..5f2d71f1 100644 --- a/server/pkg/starter/run.go +++ b/server/pkg/starter/run.go @@ -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) } // 参数校验器初始化、如错误提示中文转译等 diff --git a/server/pkg/starter/web-server.go b/server/pkg/starter/web-server.go index bc6cac19..193b4073 100644 --- a/server/pkg/starter/web-server.go +++ b/server/pkg/starter/web-server.go @@ -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 { diff --git a/server/pkg/utils/jsonx/jsonx.go b/server/pkg/utils/jsonx/jsonx.go index 6a8bccd5..1d408048 100644 --- a/server/pkg/utils/jsonx/jsonx.go +++ b/server/pkg/utils/jsonx/jsonx.go @@ -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) diff --git a/server/pkg/utils/netx/netx.go b/server/pkg/utils/netx/netx.go index 899140e3..9da2aa91 100644 --- a/server/pkg/utils/netx/netx.go +++ b/server/pkg/utils/netx/netx.go @@ -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 diff --git a/server/pkg/utils/runtimex/runtimex.go b/server/pkg/utils/runtimex/runtimex.go new file mode 100644 index 00000000..f1406494 --- /dev/null +++ b/server/pkg/utils/runtimex/runtimex.go @@ -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 +} diff --git a/server/pkg/validatorx/pattern.go b/server/pkg/validatorx/pattern.go index e94dc639..4f8c3acf 100644 --- a/server/pkg/validatorx/pattern.go +++ b/server/pkg/validatorx/pattern.go @@ -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 } diff --git a/server/pkg/ws/ws.go b/server/pkg/ws/ws.go index d346eeec..f1be7a7f 100644 --- a/server/pkg/ws/ws.go +++ b/server/pkg/ws/ws.go @@ -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()