Golang Zap 日志配置
Golang 日志库 Zap 和 官方日志库改造
安装依赖
go get gopkg.in/natefinch/lumberjack.v2
go get go.uber.org/zap
go get go.uber.org/zap/zapcore
示例使用了viper
来读取配置文件
go get github.com/spf13/viper
使用方式
- 拷贝配置文件到项目中,新建一个 go 文件存放,package 名称可以自定义
- 在项目启动位置或者调用日志对象之前执行
NewLogger
函数,来创建日志对象
下面给出在 gin 框架中的使用方式。
var (
engine *gin.Engine
)
func init() {
// 配置文件初始化
viper.SetConfigName("config")
viper.SetConfigType("json")
viper.AddConfigPath("./debug")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
fmt.Println("配置文件读取失败: " + err.Error())
os.Exit(1)
}
// 日志配置
err := os.Setenv("GIN_DEBUG", "true")
if err != nil {
panic(err)
}
logger.InitLogger()
gin.SetMode(gin.ReleaseMode)
engine = gin.Default()
}
func main() {
listenPort := viper.GetInt("server.port")
logger.Info("服务器运行中... 端口: " + strconv.Itoa(listenPort))
if err := engine.Run(":" + strconv.Itoa(listenPort)); err != nil {
logger.Fatal("服务器运行错误: " + err.Error())
}
}
配置内容
package logger
/*
系统环境变量:
需要在 main 包的 init 函数中使用 os.Setenv() 设置环境变量
GIN_LOG_PATH: 设置文件路径及其名称,例如: ./log/server.log 或者 /var/log/demo_server.log
GIN_DEBUG: 设置是否开启开发模式,开启后会输出堆栈跟踪信息
依赖:
gopkg.in/natefinch/lumberjack.v2
go.uber.org/zap
*/
import (
"gopkg.in/natefinch/lumberjack.v2"
"os"
"sync"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
_logger *zap.Logger
once sync.Once
)
var (
Debug func(msg string, fields ...zap.Field)
Info func(msg string, fields ...zap.Field)
Warn func(msg string, fields ...zap.Field)
Error func(msg string, fields ...zap.Field)
Fatal func(msg string, fields ...zap.Field)
)
func InitLogger() {
once.Do(func() {
_logger = logConfig()
})
Debug = _logger.Debug
Info = _logger.Info
Warn = _logger.Warn
Error = _logger.Error
Fatal = _logger.Fatal
}
// LogConfig 日志配置
func logConfig() *zap.Logger {
filePath := os.Getenv("GIN_LOG_PATH")
if filePath == "" {
filePath = "./logs/server.log"
}
loggerFileWriter := lumberjack.Logger{
Filename: filePath, // 日志文件路径
MaxSize: 10, // 每个日志文件保存的最大尺寸 单位:M
MaxBackups: 5, // 日志文件最多保存多少个备份
MaxAge: 7, // 文件最多保存多少天
Compress: true, // 是否压缩
}
// 日志文件输出配置
fileEncoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "line",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder, // 全大写日志等级标识
EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"), // 时间格式
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: zapcore.FullNameEncoder,
}
// 终端输出配置
stdEncoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "line",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"),
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: zapcore.FullNameEncoder,
}
fileEncoder := zapcore.NewJSONEncoder(fileEncoderConfig) // 设置日志文件内容编码格式:json格式
stdEncoder := zapcore.NewConsoleEncoder(stdEncoderConfig) // 终端格式
// 创建写入的目标 writer
fileWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(&loggerFileWriter))
stdWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout))
// 设置同时写入文件和终端
// logWriter := zapcore.NewMultiWriteSyncer(zapcore.AddSync(&hook), zapcore.AddSync(os.Stdout))
// 定义日志级别
debugLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level >= zapcore.DebugLevel
})
infoLevel := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level >= zapcore.InfoLevel
})
// 组合所有配置,分别设置写入文件的参数和显示到终端的参数
core := zapcore.NewTee(
zapcore.NewCore(fileEncoder, fileWriter, infoLevel),
zapcore.NewCore(stdEncoder, stdWriter, debugLevel),
)
if os.Getenv("GIN_DEBUG") == "true" {
// 开启开发模式,堆栈跟踪
caller := zap.AddCaller()
// // 开启文件及行号
development := zap.Development()
// 构造日志对象
return zap.New(core, caller, development)
} else {
return zap.New(core)
}
}
Gin 日志中间件
func ginLog() gin.HandlerFunc {
return func(ctx *gin.Context) {
st := time.Now()
ctx.Next()
et := time.Now()
latency := et.Sub(st)
status := ctx.Writer.Status()
clientIP := ctx.ClientIP()
method := ctx.Request.Method
path := ctx.Request.URL.Path
// userAgent := ctx.Request.UserAgent()
if status >= 400 {
logger.Error(fmt.Sprintf("[GIN] %15s %7s [%d] %s %s", clientIP, method, status, path, latency))
} else {
logger.Info(fmt.Sprintf("[GIN] %15s %7s [%d] %s %s", clientIP, method, status, path, latency))
}
}
}
官方日志库
简单加上日志滚动,并给 gin 单独一个前缀显示
2024-03-07 15:25 更新:go 1.21 版本标准库正式提供结构化日志包 log/slog
包。推荐使用
package logger
import (
"fmt"
"io"
"log"
"os"
"github.com/spf13/viper"
"gopkg.in/natefinch/lumberjack.v2"
)
var (
Info *log.Logger
Warning *log.Logger
Error *log.Logger
Debug *log.Logger
Gin *log.Logger
)
func Init() {
filePath := fmt.Sprintf("%s%s", viper.GetString("log.dir"), viper.GetString("log.filename"))
loggerFileWriter := &lumberjack.Logger{
Filename: filePath, // 日志文件路径
MaxSize: 5, // 每个日志文件保存的最大尺寸 单位:M
MaxBackups: 3, // 日志文件最多保存多少个备份
MaxAge: 0, // 文件最多保存多少天,设置为 0 不删除任何超过时间范围的文件
Compress: true, // 是否压缩
}
lw := io.MultiWriter(os.Stdout, loggerFileWriter)
Info = log.New(lw, "[INFO] ", log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(lw, "[WARNING] ", log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(lw, "[ERROR] ", log.Ldate|log.Ltime|log.Llongfile)
Debug = log.New(lw, "[DEBUG] ", log.Ldate|log.Ltime|log.Lshortfile)
Gin = log.New(lw, "[GIN] ", log.Ldate|log.Ltime)
}
gin 的日志中间件设置
func Init(engine *gin.Engine) {
engine.Use(
ginLog(),
gin.Recovery(),
)
}
func ginLog() gin.HandlerFunc {
return func(ctx *gin.Context) {
st := time.Now()
ctx.Next()
et := time.Now()
latency := et.Sub(st)
status := ctx.Writer.Status()
clientIP := ctx.ClientIP()
method := ctx.Request.Method
path := ctx.Request.URL.Path
// userAgent := ctx.Request.UserAgent()
if status >= 400 {
logger.Gin.Printf("%15s %7s [%d] %s %s", clientIP, method, status, path, latency)
} else {
logger.Gin.Printf("%15s %7s [%d] %s %s", clientIP, method, status, path, latency)
}
}
}
效果如下
[GIN-debug] Listening and serving HTTP on :59780
[GIN] 2022/10/08 17:11:28 127.0.0.1 GET [200] /ping 54.958µs
[GIN] 2022/10/08 17:11:34 127.0.0.1 GET [200] /ping 70.542µs
[GIN] 2022/10/08 17:11:34 127.0.0.1 GET [200] /ping 34.583µs
[GIN] 2022/10/08 17:11:36 127.0.0.1 GET [200] /ping 36.042µs
[GIN] 2022/10/08 17:11:42 127.0.0.1 GET [404] /piasd 1.417µs