介绍
日志库是每个编程语言必备的库, 在 go 语言中, 知名的有3个
- slog 标准库(内置/简单/结构化), 之前学习标准库时已经用过了
- logrus 是一个比较受欢迎的日志库
- zap 是一个高性能的日志库 https://www.bilibili.com/video/BV1mNnwzJEN5
logrus
虽然是比较受欢迎的一个库, 用法也比较简单, 但是性能不如 slog 和 zap
安装
sh
go get github.com/sirupsen/logrus常用方法
go
package main
import (
"fmt"
"github.com/sirupsen/logrus"
)
func main() {
logrus.SetLevel(logrus.DebugLevel) // 设置日志的输出等级
logrus.Debug("debug")
logrus.Println("hello", "world")
logrus.Info("info")
logrus.Warn("warn")
logrus.Error("error")
// logrus.Fatal("fatal") // 程序会退出
// logrus.Panic("panic") // 程序会退出, 并且输出详细堆栈
fmt.Println(logrus.GetLevel()) // 获取日志的输出等级
}设置固定字段
与 slog 类似, 会返回新的 logger 实例
go
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
logger := logrus.WithField("AppName", "logrus-demo").WithField("scope", "web")
logger.Info("info")
// INFO[0000] info AppName=logrus-demo scope=web
// 或者也可以使用 WithField
logger2 := logrus.WithFields(logrus.Fields{
"type": "video",
})
logger2.Info("info2")
// INFO[0000] info2 type=video
}设置输出格式
go
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: true, // 在终端中显示时, 是否显示不同颜色(比如: Info蓝色, Warn黄色)
FullTimestamp: true, // 是否显示完整的时间戳
TimestampFormat: "2006-01-02 15:04:05", // 时间戳格式化
})
logrus.Info("log message")
logrus.Warn("log message")
// INFO[0000] log message
logrus.SetFormatter(&logrus.JSONFormatter{}) // 以json格式输出
logrus.Info("log message")
// {"level":"info","msg":"log message","time":"2026-04-21T21:47:30+08:00"}
}日志双写
go
package main
import (
"fmt"
"io"
"os"
"github.com/sirupsen/logrus"
)
func main() {
file, err := os.OpenFile("logs/app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
if err != nil {
fmt.Println("日志文件创建失败:", err)
return
}
logrus.SetFormatter(&logrus.JSONFormatter{}) // 以json格式输出
logrus.SetOutput(file) // 日志仅输出到文件
logrus.Info("这个日志消息不会输出到标准输出")
mw := io.MultiWriter(os.Stdout, file)
logrus.SetOutput(mw) // 同时输出到文件和标准输出
logrus.Info("some log message")
}报告输入日志的源码位置
类似 slog 的 AddSource 选项
go
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
// 显示记录日志的源码位置
logrus.SetReportCaller(true)
logrus.Info("some log message")
// INFO[0000]/Users/secret/codes/logrus-demo/main.go:10 main.main() some log message
}钩子函数 Hooks
go
package main
import (
"fmt"
"github.com/sirupsen/logrus"
)
type CustomLogHook struct{}
// 这个方法决定了, 什么级别的日志会触发 CustomLogHook
func (hook *CustomLogHook) Levels() []logrus.Level {
// return logrus.AllLevels // 表示所有等级都触发
return []logrus.Level{ // 表示只有 Warn 和 Error 触发
logrus.WarnLevel,
logrus.ErrorLevel,
}
}
// 这个方法决定了, 触发 CustomLogHook 会执行什么
func (hook *CustomLogHook) Fire(entry *logrus.Entry) error {
fmt.Println("CustomLogHook-执行了", entry.Message, entry.Time.Format("2006-01-02 15:04:05"))
return nil
}
func main() {
logrus.AddHook(&CustomLogHook{})
logrus.Info("hello")
logrus.Warn("world")
// 控制台输出如下: 说明当执行 logrus.Warn 时 CustomLogHook#Fire 方法执行了
// INFO[0000] hello
// CustomLogHook-执行了 world 2026-04-21 22:25:06
// WARN[0000] world
}go
package main
import (
"fmt"
"os"
"github.com/sirupsen/logrus"
)
type CustomLogHook struct {
File *os.File
}
func (hook *CustomLogHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.WarnLevel,
logrus.ErrorLevel,
}
}
func (hook *CustomLogHook) Fire(entry *logrus.Entry) error {
fmt.Println("CustomLogHook-执行了", entry.Level)
var filePath string
if entry.Level == logrus.WarnLevel {
// 写入 app-warn.log
filePath = "logs/app-warn.log"
} else {
// 写入 app-error.log
filePath = "logs/app-error.log"
}
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
if err != nil {
return err
}
line, err := entry.String() // 获取到日志
if err != nil {
return err
}
file.WriteString(line) // 将日志消息写入文件
return nil
}
func main() {
logrus.AddHook(&CustomLogHook{})
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.Info("hello") // 这个不会触发 hook
logrus.Warn("11111") // 写入 app-warn.log
logrus.Error("2222") // 写入 app-error.log
}zap
非常推荐使用, 因为速度奇快
安装
sh
go get -u go.uber.org/zap基本使用
go
package main
import (
"fmt"
"go.uber.org/zap"
)
func main() {
// 使用 example 配置创建 logger 实例
logger := zap.NewExample()
logger.Debug("debug message")
logger.Info("info message")
logger.Warn("warn message")
logger.Error("error message")
// logger.Panic("Panic message") // 程序会退出,并输出详细堆栈信息
// logger.Fatal("Fatal message") // 程序会直接退出
// 使用 Development 配置创建 logger 实例, 适合开发环境
devLogger, err := zap.NewDevelopment()
if err != nil {
fmt.Println(err)
return
}
devLogger.Debug("debug message")
devLogger.Info("info message")
devLogger.Warn("warn message")
devLogger.Error("error message") // 这个和 exampleLogger 不一样, 会除数详细错误信息
// 使用 Production 配置创建 logger 实例, 适合线上环境
// 默认会输出 json 格式的日志
prodLogger, err := zap.NewProduction()
if err != nil {
fmt.Println(err)
return
}
prodLogger.Debug("debug message")
prodLogger.Info("info message")
prodLogger.Warn("warn message")
prodLogger.Error("error message") // 这个和 exampleLogger 不一样, 会除数详细错误信息
}设置日志级别
要设置日志级别, 就要使用自定义的配置创建 logger 实例, 而不能使用之前的哪些预配置方法
go
package main
import (
"fmt"
"go.uber.org/zap"
)
func main() {
logCfg := zap.NewDevelopmentConfig()
logCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) // 设置日志级别
logger, err := logCfg.Build()
if err != nil {
fmt.Println("日志对象构建失败", err)
return
}
logger.Debug("debug message") // 这个日志不会输出,因为设置了 Level 为 InfoLevel
logger.Info("info message")
}时间格式化
go
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
logCfg := zap.NewDevelopmentConfig()
logCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) // 设置日志级别
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") // 格式化时间
logger, err := logCfg.Build()
if err != nil {
fmt.Println("日志对象构建失败", err)
return
}
logger.Debug("debug message")
logger.Info("info message")
}标准日志与 sugar 日志
go
package main
import (
"fmt"
"go.uber.org/zap"
)
func main() {
logger, err := zap.NewProduction()
if err != nil {
fmt.Println("logger build failed", err)
return
}
// 标准日志就只能传一个字符串, 而且必须自己手动格式化
logger.Info("info message")
logger.Info(fmt.Sprintf("info message %s %d", "str-value", 11))
// 要传变量就要使用 zap.String 等辅助方法增加字段
logger.Info("info message", zap.String("str", "str-value"))
logger.Info("info message", zap.Int("int", 11))
fmt.Println("======================================")
// sugar 日志可以直接格式化
sl := logger.Sugar()
sl.Infof("info message %s %d", "str-value", 11)
// sugar 日志增加字段就更简单, 直接 key,val
sl.Infow("info message", "str", "str-value", "int", 11)
}自定义日志前缀
go
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
type CustomEncoder struct {
zapcore.Encoder
}
func (ce CustomEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
buf, err := ce.Encoder.EncodeEntry(ent, fields)
if err != nil {
return buf, err
}
// fmt.Println("CustomEncoder#EncodeEntry")
logPrefix := "[App]"
logString := buf.String()
buf.Reset()
buf.AppendString(logPrefix + logString)
return buf, nil
}
func main() {
encoder := CustomEncoder{
Encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
}
core := zapcore.NewCore(encoder, os.Stdout, zap.NewAtomicLevelAt(zap.InfoLevel))
logger := zap.New(core, zap.AddCaller()) // zap.AddCaller() 输出日志的源代码位置
// logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
// zap.AddStacktrace 表示输出堆栈信息
logger.Info("info message")
}全局日志
go
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func InitLogger() {
// 1.实例化一个 logger 并设置日志级别和时间格式化
logCfg := zap.NewProductionConfig()
logCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) // 设置日志级别
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") // 格式化时间
logger, err := logCfg.Build()
if err != nil {
fmt.Println("日志对象构建失败", err)
return
}
// 2.ReplaceGlobals 将 logger 设置为全局日志实例
zap.ReplaceGlobals(logger)
}
func main() {
InitLogger()
zap.L().Info("info message 1") // zap.L() 获取全局标准日志实例
zap.S().Info("info message 2") // zap.S() 获取全局sugar日志实例
// zap.L()/zap.S() 可以在其他文件/方法中使用
}日志双写
go
package main
import (
"fmt"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func InitLogger() {
// 1.构建一个自定义配置
logCfg := zap.NewDevelopmentConfig()
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
file, err := os.OpenFile("logs/app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666)
if err != nil {
fmt.Println("日志文件打开失败", err)
return
}
// 2.构建一个 zapcore.Core 实例, 配置双写同步器
// MultiWriteSyncer 将日志同时输出到标准输出和文件
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(logCfg.EncoderConfig),
zapcore.NewMultiWriteSyncer(file, os.Stdout),
zapcore.InfoLevel,
)
// 3.根据 Core实例构建一个 zap.Logger 实例
logger := zap.New(core, zap.AddCaller())
// 4.ReplaceGlobals 将 logger 设置为全局日志实例
zap.ReplaceGlobals(logger)
}
func main() {
InitLogger()
zap.L().Info("info message") // 这个日志将会同时输出到标准输出(控制台)和文件内容
}日志时间分片
按照日期将每天的日志分别记录到不同的日志文件中, 也可以 按照官方文档的方式实现
go
package main
import (
"fmt"
"os"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// 自定义写入文件的实现
type CustomWriter struct {
file *os.File
date string
mutex sync.Mutex
}
func (cw *CustomWriter) Write(p []byte) (n int, err error) {
cw.mutex.Lock()
defer cw.mutex.Unlock()
today := time.Now().Format("2006-01-02")
if cw.date == today {
return cw.file.Write(p)
}
if cw.file != nil {
cw.file.Close()
}
filePath := fmt.Sprintf("logs/app-%s.log", today)
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666)
if err != nil {
fmt.Println("日志文件打开失败", err)
return
}
cw.date = today
cw.file = file
return file.Write(p)
}
func InitLogger() {
// 1.构建一个自定义配置
logCfg := zap.NewProductionConfig()
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
// 2.构建一个 zapcore.Core 实例, 配置双写同步器
// MultiWriteSyncer 将日志同时输出到标准输出和文件
// 使用自定义写入文件的实现
core := zapcore.NewCore(
// zapcore.NewConsoleEncoder(logCfg.EncoderConfig),
zapcore.NewJSONEncoder(logCfg.EncoderConfig),
zapcore.NewMultiWriteSyncer(os.Stdout, zapcore.AddSync(&CustomWriter{})),
zapcore.InfoLevel,
)
// 3.根据 Core实例构建一个 zap.Logger 实例
logger := zap.New(core, zap.AddCaller())
// 4.ReplaceGlobals 将 logger 设置为全局日志实例
zap.ReplaceGlobals(logger)
}
func main() {
InitLogger()
zap.L().Info("info message") // 这个日志将会同时输出到标准输出(控制台)和文件内容
}go
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func InitLogger() {
// 1.构建一个自定义配置
logCfg := zap.NewProductionConfig()
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
// 2.构建一个 zapcore.Core 实例, 配置双写同步器
// MultiWriteSyncer 将日志同时输出到标准输出和文件
// 注: 需要先安装 go get gopkg.in/natefinch/lumberjack.v2
writer := zapcore.AddSync(&lumberjack.Logger{
Filename: "logs/app.log",
MaxSize: 100, // 日志文件最大大小 100M
MaxAge: 7, // 日志保留时间 7天
LocalTime: true, // 日志使用本地时间
Compress: true, // 日志压缩
})
core := zapcore.NewCore(
// zapcore.NewConsoleEncoder(logCfg.EncoderConfig),
zapcore.NewJSONEncoder(logCfg.EncoderConfig),
zapcore.NewMultiWriteSyncer(os.Stdout, writer),
zapcore.InfoLevel,
)
// 3.根据 Core实例构建一个 zap.Logger 实例
logger := zap.New(core, zap.AddCaller())
// 4.ReplaceGlobals 将 logger 设置为全局日志实例
zap.ReplaceGlobals(logger)
}
func main() {
InitLogger()
zap.L().Info("info message") // 这个日志将会同时输出到标准输出(控制台)和文件内容
}日志级别分片
将不同级别的日志分别记录到不同的文件中
go
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
type CustomEncoder struct {
zapcore.Encoder
warnFile *os.File
errorFile *os.File
}
func (ce CustomEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
buf, err := ce.Encoder.EncodeEntry(ent, fields)
if err != nil {
return buf, err
}
switch ent.Level {
case zapcore.WarnLevel:
if ce.warnFile == nil {
ce.warnFile, _ = os.OpenFile("logs/app-warn.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
}
ce.warnFile.Write(buf.Bytes())
break
case zapcore.ErrorLevel:
if ce.errorFile == nil {
ce.errorFile, _ = os.OpenFile("logs/app-error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
}
ce.errorFile.Write(buf.Bytes())
break
}
return buf, nil
}
func main() {
core := zapcore.NewCore(
&CustomEncoder{
// Encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
Encoder: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
},
os.Stdout,
zap.NewAtomicLevelAt(zap.InfoLevel),
)
logger := zap.New(core, zap.AddCaller())
logger.Info("info message")
logger.Warn("warn message") // 写入到 logs/app-warn.log
logger.Error("error message") // 写入到 logs/app-error.log
}实际应用&功能整合
其实就是将上述学过的功能做一个整合
- 设置级别
- 格式化时间
- 输出json
- 日志前缀
- 日志双写
- 日志时间/等级分片
- 全局日志实例设置
go
package main
import (
"fmt"
"os"
"time"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
// 5.这个 CustomLoggerEncoder 会实现日志双写功能
type CustomLoggerEncoder struct {
zapcore.Encoder
warnFile *os.File
errorFile *os.File
infoFile *os.File
date string
}
func (cle CustomLoggerEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
buf, err := cle.Encoder.EncodeEntry(ent, fields)
if err != nil {
return buf, err
}
today := time.Now().Format("2006-01-02")
// 6.日志时间分片,按照每天一个目录来做区分
// 创建今天的日志保存目录
if cle.date != today {
os.MkdirAll(fmt.Sprintf("logs/%s", today), 0o777)
cle.date = today
}
// 4.修改日志内容: 统一增加前缀
logPrefix := "[App]"
logString := buf.String()
buf.Reset()
buf.AppendString(logPrefix + logString)
// 6.日志等级分片
switch ent.Level {
case zapcore.WarnLevel:
if cle.warnFile == nil {
filePath := fmt.Sprintf("logs/%s/warn.log", today)
cle.warnFile, _ = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
}
cle.warnFile.Write(buf.Bytes())
break
case zapcore.ErrorLevel:
if cle.errorFile == nil {
filePath := fmt.Sprintf("logs/%s/error.log", today)
cle.errorFile, _ = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
}
cle.errorFile.Write(buf.Bytes())
break
default:
if cle.infoFile == nil {
filePath := fmt.Sprintf("logs/%s/info.log", today)
cle.infoFile, _ = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
}
cle.infoFile.Write(buf.Bytes())
}
return buf, nil
}
func InitLogger() {
logCfg := zap.NewProductionConfig()
logCfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") // 2.格式化时间
// 3.使用自定义的 Encoder, 输出 json
encoder := &CustomLoggerEncoder{
Encoder: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
}
// 构建自定义的 Core
core := zapcore.NewCore(
encoder,
zapcore.AddSync(os.Stdout),
zap.NewAtomicLevelAt(zap.InfoLevel), // 1.设置日志级别
)
logger := zap.New(core, zap.AddCaller())
zap.ReplaceGlobals(logger) // 7.全局日志实例设置
}
func main() {
InitLogger()
zap.L().Info("info message")
zap.L().Warn("warn message")
zap.L().Error("error message")
}