写Go程序的人几乎都绕不开日志这件事。标准库的log包虽然开箱即用,但功能实在简陋:不支持日志级别、不能结构化输出、没有钩子机制,等项目规模一大,排查问题就像大海捞针。这篇文章就带你从标准库讲起,一步步完成第三方日志库的安装和配置,让调试效率真正提上来。

先看看标准库log能做什么
标准库的log包使用非常简单,导入即可打印日志:
package main
import "log"
func main() {
log.Println("服务启动中...")
log.Printf("监听端口: %d", 8080)
log.Fatal("发生致命错误,程序退出")
}它还提供了log.SetOutput来指定输出目标,log.SetFlags来设置时间格式和文件行号:
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetPrefix("[MyApp] ")这套东西写小工具够用了,但问题也很明显:没有日志级别区分,Debug信息和Error信息混在一起;输出的是纯文本,没法接入ELK这类日志采集系统做结构化分析;性能上每一次调用都带格式化,高并发场景下开销不小。所以稍微正式一点的项目,基本都会换第三方库。
安装并配置logrus
logrus是Go社区里资历最老的日志库之一,API设计与标准库高度兼容,迁移成本低。安装只需要一条命令:
go get github.com/sirupsen/logrus
装好后基本使用也很直观:
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"user": "zhangsan",
"port": 8080,
}).Info("用户请求处理完成")
}实际项目中一般会在入口处做一次统一配置,包括设置输出格式、日志级别和输出目标:
func initLog() {
// 设置为JSON格式,方便日志采集系统解析
log.SetFormatter(&log.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置最低输出级别
log.SetLevel(log.InfoLevel)
// 同时输出到文件和控制台
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
log.SetOutput(io.MultiWriter(os.Stdout, file))
}logrus支持Trace、Debug、Info、Warn、Error、Fatal、Panic七个级别,结构化字段通过WithFields传递,这在排查线上问题时特别有用——你可以直接按某个字段过滤出相关日志。它的缺点是性能一般,反射和格式化开销在高吞吐场景会成为瓶颈。
高性能之选:zap的安装与配置
Uber开源的zap主打零分配的高性能,官方基准测试显示它比普通日志库快一个数量级。安装分两步:
go get go.uber.org/zap
zap有两种使用模式:开发时的zap.NewDevelopment()和生产环境的zap.NewProduction()。生产模式下默认输出JSON格式。自定义配置的写法如下:
func newLogger() *zap.Logger {
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{"stdout", "app.log"}
cfg.EncoderConfig.TimeKey = "time"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := cfg.Build(zap.AddCaller())
if err != nil {
panic(err)
}
defer logger.Sync()
return logger
}
// 使用示例
logger.Info("订单创建成功",
zap.String("order_id", "A10086"),
zap.Int("amount", 199),
)需要强类型字段时zap要求用zap.String、zap.Int这类方法,写起来比logrus啰嗦一点,但换来的是极低的内存分配。如果嫌麻烦,可以用zap.SugaredLogger,它支持logger.Sugar().Infof("订单号: %s", id)这种格式化写法,性能略低但依然够快。
文件轮转也是生产环境必备能力,zap本身不带,通常配合lumberjack使用:
lumberJackLogger := &lumberjack.Logger{
Filename: "logs/app.log",
MaxSize: 100, // 单文件最大100MB
MaxBackups: 5, // 保留5个备份
MaxAge: 30, // 保留30天
Compress: true,
}怎么选:logrus还是zap
从性能角度看,zap明显占优。它的零分配设计让每次日志调用的内存分配接近于零,而logrus每次调用都会产生额外分配。在每秒十万级日志量的服务里,这个差距会被放大成实实在在的CPU和GC压力。
但选型不能只看跑分。如果你的团队已经大量使用logrus,项目日志量不大,追求的是易读易写,那继续用logrus完全没问题,它的API更友好,社区文档也更丰富。如果是高并发服务、微服务网关或者对延迟敏感的组件,zap是更稳妥的选择。
另外提一句Goland的日志调试技巧:在IDE里给日志语句打断点后,可以用Evaluate Expression功能动态查看字段值,配合结构化日志输出,基本能覆盖大部分调试场景。无论选哪个库,建议在项目一开始就统一封装一个日志入口包,避免各处直接import不同库造成混乱,后期想切换方案时也只需改一个地方。