【Go】基于 Gin 从0到1搭建 Web 管理后台系统后端服务(一)项目初始化、配置和日志(下)

简介: 【Go】基于 Gin 从0到1搭建 Web 管理后台系统后端服务(一)项目初始化、配置和日志(下)

3、日志初始化

为什么需要记录日志?

在Go语言中记录日志是一个非常常见的行为。以下是几个原因:

  1. 问题排查:在应用程序出现故障或错误时,日志记录可以帮助我们定位问题所在。我们可以通过查看日志记录,了解应用程序在何时、何处出现问题,从而更快地进行故障排查。
  2. 性能分析:日志记录也可以用于分析应用程序的性能。我们可以在代码中记录应用程序执行过程中的关键事件和时间戳,然后使用这些信息来分析和优化应用程序的性能。
  3. 安全监控:日志记录还可以用于监控应用程序的安全性。我们可以记录所有的访问请求和响应,以便在应用程序受到攻击时能够快速响应。
  4. 后续分析:日志记录还可以用于后续分析和审计。我们可以将日志记录存储在外部数据库中,以便在未来需要时能够查询和分析。

值得注意的是,日志记录是一个非常重要的行为,可以帮助我们快速排查问题、优化性能、监控安全性以及进行后续分析和审计。因此,在Go语言中记录日志是一个必要的步骤。

这里将使用 zap 作为日志库,一般来说,日志都是需要写入到文件保存的,这也是 zap 唯一缺少的部分,所以我将结合 lumberjack 来使用,实现日志切割归档的功能。使用Zap作为日志记录器,有以下几个原因:

  1. 高性能:Zap是一个非常高性能的日志库,其性能比其他日志库(如logrus和zap)更高。这主要是由于Zap使用了零内存分配的技术,可以在不影响性能的情况下减少垃圾回收。
  2. 丰富的功能:Zap支持多种日志级别、多种输出格式(如JSON、Console、Logfmt等)、多种输出位置(如控制台、文件、网络等),还支持自定义日志字段和Hook。
  3. 安全性:Zap采用了严格的日志记录策略,确保日志安全性。它可以避免由于多协程写入日志导致的竞态条件,还可以自动切分日志文件,避免单个日志文件过大导致的性能问题和磁盘空间浪费。
  4. 社区支持:Zap是由Uber开发并维护的开源日志库,其开发和维护的活跃度和社区支持度都非常高。因此,Zap可以得到广泛的支持和使用,并有大量的第三方库和框架与之兼容。

而Lumberjack是一个用于高性能、轮转和滚动日志文件的库。在Zap中使用Lumberjack有以下几个好处:

  1. 高性能:Lumberjack使用了缓冲区和异步写入等技术,可以减少IO的次数,提高写入日志的性能。
  2. 轮转和滚动:Lumberjack可以自动进行日志文件的轮转和滚动,可以防止单个日志文件变得过大而影响性能,也可以方便地管理和备份历史日志。
  3. 稳定性和可靠性:Lumberjack可以确保即使在写入日志的同时发生了故障,也不会丢失日志数据。此外,Lumberjack还可以处理文件系统的错误和日志文件权限等问题。

在Zap中使用Lumberjack可以提高日志的性能、稳定性和可靠性,并且方便管理和备份历史日志。

安装zap:

go get -u go.uber.org/zap
go get -u gopkg.in/natefinch/lumberjack.v2

3.1 定义配置项

config.yarml 中新增日志配置:

# ...
zap: # 日志配置
  level: info # 日志级别
  prefix: '[east_white_common_admin/server]' # 日志前缀
  format: console # 输出
  director: log # 日志存放的文件
  encode_level: LowercaseColorLevelEncoder # 编码级别
  stacktrace_key: stacktrace # 栈名
  max_age: 0 # 日志留存时间
  show_line: true # 显示行
  log_in_console: true # 输出控制台

然后新建 config/zap.go,在文件中增加对应的结构体和日志级别转换方法:

package config
import (
   "strings"
   "go.uber.org/zap/zapcore"
)
type Zap struct {
   Level         string `mapstructure:"level" json:"level" yaml:"level"`                            // 级别
   Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`                         // 日志前缀
   Format        string `mapstructure:"format" json:"format" yaml:"format"`                         // 输出
   Director      string `mapstructure:"director" json:"director"  yaml:"director"`                  // 日志文件夹
   EncodeLevel   string `mapstructure:"encode_level" json:"encode_level" yaml:"encode_level"`       // 编码级
   StacktraceKey string `mapstructure:"stacktrace_key" json:"stacktrace_key" yaml:"stacktrace_key"` // 栈名
   MaxAge       int  `mapstructure:"max_age" json:"max_age" yaml:"max_age"`                      // 日志留存时间
   ShowLine     bool `mapstructure:"show_line" json:"show_line" yaml:"show_line"`                // 显示行
   LogInConsole bool `mapstructure:"log_in_console" json:"log_in_console" yaml:"log_in_console"` // 输出控制台
}
// ZapEncodeLevel 根据 EncodeLevel 返回 zapcore.LevelEncoder
func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder {
   switch {
   case z.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
      return zapcore.LowercaseLevelEncoder
   case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
      return zapcore.LowercaseColorLevelEncoder
   case z.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
      return zapcore.CapitalLevelEncoder
   case z.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
      return zapcore.CapitalColorLevelEncoder
   default:
      return zapcore.LowercaseLevelEncoder
   }
}
// TransportLevel 根据字符串转化为 zapcore.Level
func (z *Zap) TransportLevel() zapcore.Level {
   z.Level = strings.ToLower(z.Level)
   switch z.Level {
   case "debug":
      return zapcore.DebugLevel
   case "info":
      return zapcore.InfoLevel
   case "warn":
      return zapcore.WarnLevel
   case "error":
      return zapcore.WarnLevel
   case "dpanic":
      return zapcore.DPanicLevel
   case "panic":
      return zapcore.PanicLevel
   case "fatal":
      return zapcore.FatalLevel
   default:
      return zapcore.DebugLevel
   }
}

别忘了在 config.go 中加入 zap

package config
type Configuration struct {
   App App `mapstructure:"app" json:"app" yaml:"app"`
   Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"`
}

3.2 日志初始化方法

接下来就是写日志初始化方法了,在 core 中新建 zap.go

package core
import (
   "ewa_admin_server/core/internal"
   "ewa_admin_server/global"
   "ewa_admin_server/utils"
   "fmt"
   "os"
   "go.uber.org/zap"
   "go.uber.org/zap/zapcore"
)
// InitializeZap Zap 获取 zap.Logger
func InitializeZap() (logger *zap.Logger) {
   if ok, _ := utils.PathExists(global.EWA_CONFIG.Zap.Director); !ok { // 判断是否有Director文件夹
      fmt.Printf("create %v directory\n", global.EWA_CONFIG.Zap.Director)
      _ = os.Mkdir(global.EWA_CONFIG.Zap.Director, os.ModePerm)
   }
   cores := internal.Zap.GetZapCores()
   logger = zap.New(zapcore.NewTee(cores...))
   if global.EWA_CONFIG.Zap.ShowLine {
      logger = logger.WithOptions(zap.AddCaller())
   }
   fmt.Println("====2-zap====: zap log init success")
   return logger
}

core/internal 中新建 zap.go

package internal
import (
   "ewa_admin_server/global"
   "fmt"
   "time"
   "go.uber.org/zap"
   "go.uber.org/zap/zapcore"
)
var Zap = new(_zap)
type _zap struct{}
// GetEncoder 获取 zapcore.Encoder
func (z *_zap) GetEncoder() zapcore.Encoder {
   if global.EWA_CONFIG.Zap.Format == "json" {
      return zapcore.NewJSONEncoder(z.GetEncoderConfig())
   }
   return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
}
// GetEncoderConfig 获取zapcore.EncoderConfig
func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
   return zapcore.EncoderConfig{
      MessageKey:     "message",
      LevelKey:       "level",
      TimeKey:        "time",
      NameKey:        "logger",
      CallerKey:      "caller",
      StacktraceKey:  global.EWA_CONFIG.Zap.StacktraceKey,
      LineEnding:     zapcore.DefaultLineEnding,
      EncodeLevel:    global.EWA_CONFIG.Zap.ZapEncodeLevel(),
      EncodeTime:     z.CustomTimeEncoder,
      EncodeDuration: zapcore.SecondsDurationEncoder,
      EncodeCaller:   zapcore.FullCallerEncoder,
   }
}
// GetEncoderCore 获取Encoder的 zapcore.Core
func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
   writer, err := FileRotateLogs.GetWriteSyncer(l.String()) // 使用file-rotatelogs进行日志分割
   if err != nil {
      fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
      return nil
   }
   return zapcore.NewCore(z.GetEncoder(), writer, level)
}
// CustomTimeEncoder 自定义日志输出时间格式
func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
   encoder.AppendString(global.EWA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
}
// GetZapCores 根据配置文件的Level获取 []zapcore.Core
func (z *_zap) GetZapCores() []zapcore.Core {
   cores := make([]zapcore.Core, 0, 7)
   for level := global.EWA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
      cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
   }
   return cores
}
// GetLevelPriority 根据 zapcore.Level 获取 zap.LevelEnablerFunc
func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
   switch level {
   case zapcore.DebugLevel:
      return func(level zapcore.Level) bool { // 调试级别
         return level == zap.DebugLevel
      }
   case zapcore.InfoLevel:
      return func(level zapcore.Level) bool { // 日志级别
         return level == zap.InfoLevel
      }
   case zapcore.WarnLevel:
      return func(level zapcore.Level) bool { // 警告级别
         return level == zap.WarnLevel
      }
   case zapcore.ErrorLevel:
      return func(level zapcore.Level) bool { // 错误级别
         return level == zap.ErrorLevel
      }
   case zapcore.DPanicLevel:
      return func(level zapcore.Level) bool { // dpanic级别
         return level == zap.DPanicLevel
      }
   case zapcore.PanicLevel:
      return func(level zapcore.Level) bool { // panic级别
         return level == zap.PanicLevel
      }
   case zapcore.FatalLevel:
      return func(level zapcore.Level) bool { // 终止级别
         return level == zap.FatalLevel
      }
   default:
      return func(level zapcore.Level) bool { // 调试级别
         return level == zap.DebugLevel
      }
   }
}

以及 file_rotate_logs.go

package internal
import (
   "ewa_admin_server/global"
   "os"
   "path"
   "time"
   rotatelogs "github.com/lestrrat-go/file-rotatelogs"
   "go.uber.org/zap/zapcore"
)
var FileRotateLogs = new(fileRotateLogs)
type fileRotateLogs struct{}
// GetWriteSyncer 获取 zapcore.WriteSyncer
func (r *fileRotateLogs) GetWriteSyncer(level string) (zapcore.WriteSyncer, error) {
   fileWriter, err := rotatelogs.New(
      path.Join(global.EWA_CONFIG.Zap.Director, "%Y-%m-%d", level+".log"),
      rotatelogs.WithClock(rotatelogs.Local),
      rotatelogs.WithMaxAge(time.Duration(global.EWA_CONFIG.Zap.MaxAge)*24*time.Hour), // 日志留存时间
      rotatelogs.WithRotationTime(time.Hour*24),
   )
   if global.EWA_CONFIG.Zap.LogInConsole {
      return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err
   }
   return zapcore.AddSync(fileWriter), err
}

新建 utils/directory.go 文件,编写 PathExists 函数,用于判断路径是否存在:

package utils
import (
   "errors"
   "os"
)
//@function: PathExists
//@description: 文件目录是否存在
//@param: path string
//@return: bool, error
func PathExists(path string) (bool, error) {
   fi, err := os.Stat(path)
   if err == nil {
      if fi.IsDir() {
         return true, nil
      }
      return false, errors.New("存在同名文件")
   }
   if os.IsNotExist(err) {
      return false, nil
   }
   return false, err
}

3.3 定义全局变量

global/global.go 中,添加 Log 成员属性

package global
import (
   "ewa_admin_server/config"
   "go.uber.org/zap"
   "github.com/spf13/viper"
)
var (
   EWA_CONFIG config.Configuration
   EWA_VIPER  *viper.Viper
   EWA_LOG    *zap.Logger
)

3.4 测试

在 main.go 中调用日志初始化函数,并尝试写入日志:

package main
import (
   "ewa_admin_server/core"
   "ewa_admin_server/global"
   "go.uber.org/zap"
   "github.com/gin-gonic/gin"
)
const AppMode = "debug" // 运行环境,主要有三种:debug、test、release
func main() {
   gin.SetMode(AppMode)
   // TODO:1.配置初始化
   global.EWA_VIPER = core.InitializeViper()
   // TODO:2.日志
   global.EWA_LOG = core.InitializeZap()
   zap.ReplaceGlobals(global.EWA_LOG)
   global.EWA_LOG.Info("server run success on ", zap.String("zap_log", "zap_log"))
   //  TODO:3.数据库连接
   // TODO:4.其他初始化
   // TODO:5.启动服务
   core.RunServer()
}

重启项目,就会发现在根目录下生成了一个 log 文件夹,作为我们日后开发用的日志记录文件。
image.png

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
6月前
|
存储 监控 算法
防止员工泄密软件中文件访问日志管理的 Go 语言 B + 树算法
B+树凭借高效范围查询与稳定插入删除性能,为防止员工泄密软件提供高响应、可追溯的日志管理方案,显著提升海量文件操作日志的存储与检索效率。
185 2
|
7月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
411 86
|
9月前
|
人工智能 负载均衡 监控
使用 Go 和 Gin 实现高可用负载均衡代理服务器
本文基于Go语言和Gin框架,实现了一个企业级负载均衡代理服务器,支持动态路由、健康检查、会话保持等功能。具备高可用性与高性能,单节点支持100k+ QPS,延迟达亚毫秒级,并提供完整的压力测试方案与优化建议。
270 7
|
10月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:使用 Gin 快速构建 Web 服务
Gin 是一个高效、轻量级的 Go 语言 Web 框架,支持中间件机制,非常适合开发 RESTful API。本文从安装到进阶技巧全面解析 Gin 的使用:快速入门示例(Hello Gin)、定义 RESTful 用户服务(增删改查接口实现),以及推荐实践如参数校验、中间件和路由分组等。通过对比标准库 `net/http`,Gin 提供更简洁灵活的开发体验。此外,还推荐了 GORM、Viper、Zap 等配合使用的工具库,助力高效开发。
|
9月前
|
JSON JavaScript 前端开发
实现ROS系统的Websocket传输,向Web应用推送sensor_msgs::Image数据
WebSocket协议具有低延迟和高实时性的特性,适用于实时数据推送。但是,它也依赖于网络条件,因此,在通过WebSocket发送数据时,保证网络稳定性也是重要的。以上步骤为建立基本的WebSocket传输提供了框架,并可以根据实际需求进行调整和优化。
623 0
|
11月前
|
关系型数据库 MySQL Java
安装和配置JDK、Tomcat、MySQL环境,以及如何在Linux下更改后端端口。
遵循这些步骤,你可以顺利完成JDK、Tomcat、MySQL环境的安装和配置,并在Linux下更改后端端口。祝你顺利!
597 11
|
12月前
|
人工智能 小程序 NoSQL
【一步步开发AI运动小程序】二十一、如何将AI运动项目配置持久化到后端?
本文介绍基于云智「Ai运动识别引擎」的运动配置持久化方案,旨在优化小程序或Uni APP中AI运动识别能力。通过将运动检测参数(如`Key`、`Name`、`TickMode`、`rules`或`samples`)持久化到后端,可避免因频繁调整运动参数而重新发布应用,提升用户体验。持久化数据结构支持规则和姿态样本存储,适用于关系数据库、文件或文档数据库(如MongoDB)。此外,云智还提供运动自动适配工具及「AI乐运动」产品,助力快速实现AI体育、全民健身等场景。
|
存储 JSON Go
PHP 日志系统的最佳搭档:一个 Go 写的远程日志收集服务
为了不再 SSH 上去翻日志,我写了个 Go 小脚本,用来接收远程日志。PHP 负责记录日志,Go 负责存储和展示,按天存储、支持 API 访问、可远程管理,终于能第一时间知道项目炸了。
276 10
|
前端开发 NoSQL Java
【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
本文介绍了如何配置和启动基于Java的若依(RuoYi)项目,涵盖后端和前端的详细步骤。首先,准备Redis、MySQL以及IDE(如Idea和VS)。接着,通过GitHub获取代码并导入到IDE中,执行必要的SQL文件和配置数据库密码。然后,启动Redis并进行相关配置。最后,按照前端配置步骤克隆前端代码库,打开终端执行命令完成前端配置。整个过程详细记录了每一步的操作,帮助开发者顺利部署若依项目。 如果你觉得有帮助,请点赞、关注和收藏,这将是我持续分享的动力!
7744 4
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
490 0