3、日志初始化
为什么需要记录日志?
在Go语言中记录日志是一个非常常见的行为。以下是几个原因:
- 问题排查:在应用程序出现故障或错误时,日志记录可以帮助我们定位问题所在。我们可以通过查看日志记录,了解应用程序在何时、何处出现问题,从而更快地进行故障排查。
- 性能分析:日志记录也可以用于分析应用程序的性能。我们可以在代码中记录应用程序执行过程中的关键事件和时间戳,然后使用这些信息来分析和优化应用程序的性能。
- 安全监控:日志记录还可以用于监控应用程序的安全性。我们可以记录所有的访问请求和响应,以便在应用程序受到攻击时能够快速响应。
- 后续分析:日志记录还可以用于后续分析和审计。我们可以将日志记录存储在外部数据库中,以便在未来需要时能够查询和分析。
值得注意的是,日志记录是一个非常重要的行为,可以帮助我们快速排查问题、优化性能、监控安全性以及进行后续分析和审计。因此,在Go语言中记录日志是一个必要的步骤。
这里将使用 zap 作为日志库,一般来说,日志都是需要写入到文件保存的,这也是 zap 唯一缺少的部分,所以我将结合 lumberjack 来使用,实现日志切割归档的功能。使用Zap作为日志记录器,有以下几个原因:
- 高性能:Zap是一个非常高性能的日志库,其性能比其他日志库(如logrus和zap)更高。这主要是由于Zap使用了零内存分配的技术,可以在不影响性能的情况下减少垃圾回收。
- 丰富的功能:Zap支持多种日志级别、多种输出格式(如JSON、Console、Logfmt等)、多种输出位置(如控制台、文件、网络等),还支持自定义日志字段和Hook。
- 安全性:Zap采用了严格的日志记录策略,确保日志安全性。它可以避免由于多协程写入日志导致的竞态条件,还可以自动切分日志文件,避免单个日志文件过大导致的性能问题和磁盘空间浪费。
- 社区支持:Zap是由Uber开发并维护的开源日志库,其开发和维护的活跃度和社区支持度都非常高。因此,Zap可以得到广泛的支持和使用,并有大量的第三方库和框架与之兼容。
而Lumberjack是一个用于高性能、轮转和滚动日志文件的库。在Zap中使用Lumberjack有以下几个好处:
- 高性能:Lumberjack使用了缓冲区和异步写入等技术,可以减少IO的次数,提高写入日志的性能。
- 轮转和滚动:Lumberjack可以自动进行日志文件的轮转和滚动,可以防止单个日志文件变得过大而影响性能,也可以方便地管理和备份历史日志。
- 稳定性和可靠性: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
文件夹,作为我们日后开发用的日志记录文件。