【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

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
1372 0
|
11月前
|
Java 应用服务中间件 Linux
Tomcat运行日志字符错乱/项目启动时控制台日志乱码问题
总结: 通过以上几种方法,概括如下:指定编码格式、设置JVM的文件编码、修改控制台输出编码、修正JSP页面编码和设置过滤器。遵循这些步骤,你可以依次排查和解决Tomcat运行日志字符错乱及项目启动时控制台日志乱码问题。希望这些建议能对你的问题提供有效的解决方案。
2066 16
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
280 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
存储 JSON Go
PHP 日志系统的最佳搭档:一个 Go 写的远程日志收集服务
为了不再 SSH 上去翻日志,我写了个 Go 小脚本,用来接收远程日志。PHP 负责记录日志,Go 负责存储和展示,按天存储、支持 API 访问、可远程管理,终于能第一时间知道项目炸了。
305 10
|
SQL JSON 关系型数据库
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:列表查询规则指南
GoWind Admin|风行是一款开箱即用的企业级Go语言中后台框架,提供配置化、高兼容的列表查询规则,支持多条件筛选、排序分页、字段过滤等功能,兼容多种数据库,显著提升开发效率与系统可维护性。
291 0
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
246 5
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
513 0
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
543 9
|
11月前
|
监控 容灾 算法
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
本文探讨了如何高效、经济且可靠地将海外应用与基础设施日志统一采集至阿里云日志服务(SLS),解决全球化业务扩展中的关键挑战。重点介绍了高性能日志采集Agent(iLogtail/LoongCollector)在海外场景的应用,推荐使用LoongCollector以获得更优的稳定性和网络容错能力。同时分析了多种网络接入方案,包括公网直连、全球加速优化、阿里云内网及专线/CEN/VPN接入等,并提供了成本优化策略和多目标发送配置指导,帮助企业构建稳定、低成本、高可用的全球日志系统。
1092 54