Skip to content

介绍

日志库是每个编程语言必备的库, 在 go 语言中, 知名的有3个

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
}

实际应用&功能整合

其实就是将上述学过的功能做一个整合

  1. 设置级别
  2. 格式化时间
  3. 输出json
  4. 日志前缀
  5. 日志双写
  6. 日志时间/等级分片
  7. 全局日志实例设置
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")
}

Released under the MIT License.