Go语言日志记录
Go语言日志记录需根据项目需求选择合适方案:小型项目可直接使用标准库log(简单但功能有限),需结构化日志推荐logrus(通用易用)或高性能的zap(生产环境首选),Go 1.21+及以上版本应优先使用内置log/slog(兼容标准库且功能丰富);关键实践包括合理设置日志级别(生产环境禁用DEBUG/TRACE)、强制使用JSON结构化格式便于分析、实施文件轮转(按大小/时间切割)避免日志膨胀、严格过滤敏感信息(如密码、令牌),并通过模块化独立logger(如每个服务/包创建专属logger)提升可维护性,同时确保日志输出目标(文件/远程服务)配置清晰,最终实现高效故障排查与系统可观测性。
日志记录是软件开发中不可或缺的一环,它帮助开发者监控应用状态、追踪错误及优化性能。本文将详细介绍Go语言中的日志记录机制,涵盖各种日志库、适用场景、代码示例及最佳实践。
一、Go标准库log
特点
- Go语言内置标准库
- 简单易用,适合小型项目
- 不支持日志级别配置和多样化格式
适用场景
- 小型项目或快速原型开发
- 简单的调试信息记录
- 不需要复杂日志管理的场景
代码示例
package main
import (
"log"
"os"
"time"
)
func main() {
// 设置日志输出到标准输出
log.SetOutput(os.Stdout)
// 设置日志标志(日期、时间、文件名和行号)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 记录不同级别的日志
log.Println("This is a standard log message")
log.Printf("Current time: %s", time.Now().Format("2006-01-02 15:04:05"))
// 记录错误并退出
if err := someFunction(); err != nil {
log.Fatalf("Failed to execute someFunction: %v", err)
}
}
func someFunction() error {
return nil // 模拟函数
}
二、第三方日志库
1. logrus
特点
- 结构化日志库
- 支持日志级别控制
- 提供自定义格式和钩子
- 与标准库log兼容
适用场景
- 需要结构化日志的项目
- 需要日志级别控制的场景
- 需要扩展日志功能(如添加钩子)的项目
安装
go get github.com/sirupsen/logrus
代码示例
package main
import (
"fmt"
"github.com/sirupsen/logrus"
"time"
)
func main() {
// 设置日志格式为JSON
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置日志级别
logrus.SetLevel(logrus.DebugLevel)
// 创建局部logger(避免全局logger滥用)
logger := logrus.New()
logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
})
// 记录日志
logger.Info("Starting the application at", time.Now())
// 记录带字段的结构化日志
logger.WithFields(logrus.Fields{
"event": "user_login",
"ip": "192.168.1.1",
"user_id": 12345,
}).Info("User logged in successfully")
// 记录错误
if err := someFunction(); err != nil {
logger.WithError(err).Error("Failed to execute someFunction")
}
}
func someFunction() error {
return fmt.Errorf("a sample error")
}
2. zap
特点
- 高性能日志库
- 专注于速度和结构化日志
- 极低的内存分配开销
- 提供生产环境和开发环境两种配置
适用场景
- 高性能要求的应用
- 企业级应用
- 需要极低日志开销的场景
安装
go get go.uber.org/zap
代码示例
package main
import (
"go.uber.org/zap"
)
func main() {
// 创建开发模式logger(包含堆栈跟踪)
logger, err := zap.NewDevelopment()
if err != nil {
panic(err)
}
defer logger.Sync() // 确保所有日志被写入
// 创建生产模式logger(不包含堆栈跟踪,更高效)
prodLogger, err := zap.NewProduction()
if err != nil {
panic(err)
}
defer prodLogger.Sync()
// 使用SugaredLogger(更简单的API,但性能略低)
sugar := logger.Sugar()
sugar.Infow("This is a structured log",
"key1", "value1",
"key2", "value2",
)
sugar.Infof("This is an unstructured log: %s", "hello world")
// 使用标准Logger(性能更高)
prodLogger.Info("This is a structured log",
zap.String("key1", "value1"),
zap.String("key2", "value2"),
)
// 记录错误日志并添加堆栈跟踪
err = someFunction()
if err != nil {
prodLogger.Error("This is an error log", zap.Error(err), zap.Stack("stack"))
}
}
func someFunction() error {
return nil // 模拟函数
}
3. log/slog (Go 1.21+)
特点
- Go 1.21中引入的新结构化日志库
- 与标准库log包兼容
- 提供丰富的功能和灵活性
- 通过Handler处理日志记录
适用场景
- 使用Go 1.21+的项目
- 需要结构化日志但不想引入第三方库的场景
- 需要与标准库兼容的日志解决方案
代码示例
package main
import (
"log/slog"
"os"
)
func main() {
// 创建JSON处理器
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
})
// 创建文本处理器
textHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
})
// 创建Logger
textLogger := slog.New(textHandler)
jsonLogger := slog.New(jsonHandler)
// 使用Logger记录日志
textLogger.Info("hello, world", "user", os.Getenv("USER"))
jsonLogger.Info("hello, world", "user", os.Getenv("USER"))
// 使用默认Logger
slog.Info("This is a default logger message", "key", "value")
}
三、日志级别
常见日志级别
- Trace: 最详细的日志,用于深度调试
- Debug: 详细的调试信息
- Info: 一般信息,程序正常运行
- Warn: 警告信息,表明可能存在问题
- Error: 错误信息,表示程序出现了问题
- Panic: 程序即将崩溃
- Fatal: 致命错误,程序无法继续运行
适用场景
| 日志级别 | 适用场景 | 生产环境使用 |
|---|---|---|
| Trace | 深度调试,分析执行流程 | 一般不使用 |
| Debug | 开发环境,详细调试信息 | 一般不使用 |
| Info | 生产环境,关键流程记录 | 推荐使用 |
| Warn | 需要关注的异常情况 | 推荐使用 |
| Error | 影响功能的错误 | 推荐使用 |
| Fatal | 程序无法继续运行的错误 | 推荐使用 |
四、日志格式
常见格式
- 文本格式: 简单易读,适合快速查看
- JSON格式: 结构化数据,便于日志分析和处理
- 自定义格式: 根据需求定义特定格式
代码示例(自定义格式)
package main
import (
"log"
"time"
)
func main() {
// 创建自定义日志格式
customFormatter := func() func() string {
return func() string {
return time.Now().Format("2006-01-02 15:04:05") + " | "
}
}()
// 设置自定义日志前缀
log.SetPrefix(customFormatter())
// 记录日志
log.Println("Custom formatted log message")
}
五、日志输出目标
常见输出目标
- 标准输出: 控制台输出,适合开发和调试
- 文件: 日志文件,适合生产环境中记录日志
- 远程服务: 将日志发送到远程日志服务,如ELK、Splunk等
代码示例(输出到文件)
package main
import (
"log"
"os"
)
func main() {
// 创建日志文件
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer file.Close()
// 设置日志输出到文件
log.SetOutput(file)
// 记录日志
log.Println("This log is written to app.log")
}
六、结构化日志
优势
- 便于后续分析工具解析
- 可以提取特定字段进行过滤和分析
- 适合大规模日志系统
代码示例(结构化日志)
package main
import (
"github.com/sirupsen/logrus"
"time"
)
func main() {
// 设置JSON格式
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
// 记录结构化日志
log.WithFields(logrus.Fields{
"event": "user_registration",
"user_id": 7890,
"email": "user@example.com",
"ip": "192.168.1.100",
"time": time.Now().Format("2006-01-02 15:04:05"),
}).Info("User registered successfully")
}
七、日志最佳实践
1. 选择合适的日志级别
- 避免在生产环境中使用DEBUG级别
- 为不同场景设置合适的日志级别
- 根据应用需求调整日志级别
2. 使用结构化日志
- 采用结构化日志格式(如JSON)
- 有选择地添加上下文信息,避免无用日志
- 避免过度使用字段,导致日志体积膨胀
3. 日志轮转策略
- 按文件大小切割(如100MB)
- 按时间周期切割(每日或每小时)
- 保留最近30天日志,自动清理旧日志
4. 保护敏感信息
- 避免在日志中记录敏感信息(如密码、个人数据)
- 使用日志过滤器移除敏感数据
5. 日志监控
- 建立日志监控和告警机制
- 及时发现和处理问题
- 使用日志分析工具(如ELK Stack、Splunk)
6. 模块化日志管理
- 为每个模块或服务创建独立的logger实例
- 便于管理和过滤日志
- 避免全局logger滥用
八、日志库对比
| 特性 | log (标准库) | logrus | zap | log/slog (Go 1.21+) | Go-Spring::Log |
|---|---|---|---|---|---|
| 日志级别 | 不支持 | 支持 | 支持 | 支持 | 支持 |
| 结构化日志 | 不支持 | 支持 | 支持 | 支持 | 支持 |
| 性能 | 一般 | 中等 | 高 | 中等 | 高 |
| 与标准库兼容 | 是 | 部分 | 是 | 是 | 否 |
| 适用场景 | 小型项目 | 通用项目 | 高性能需求 | Go 1.21+项目 | 标签路由需求 |
| 配置复杂度 | 低 | 中 | 中 | 低 | 中 |
| 社区支持 | 高 | 高 | 高 | 高 | 中 |
九、实战案例:构建完整的日志系统
1. 基于zap的生产环境日志配置
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
// 初始化日志系统
func initLogger() *zap.Logger {
// 创建日志文件
logFile, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
// 设置日志输出
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 创建JSON编码器
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)
// 创建日志核心
core := zapcore.NewCore(
jsonEncoder,
zapcore.AddSync(logFile),
zap.NewAtomicLevelAt(zap.InfoLevel),
)
// 创建logger
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
return logger
}
func main() {
logger := initLogger()
defer logger.Sync()
// 记录日志
logger.Info("Application started", zap.String("version", "1.0.0"))
// 记录带上下文信息的日志
logger.With(
zap.String("user_id", "12345"),
zap.String("request_id", "req-12345"),
).Info("User request processed")
// 记录错误
if err := someFunction(); err != nil {
logger.Error("Failed to process request", zap.Error(err))
}
}
func someFunction() error {
return nil
}
2. 日志轮转实现
package main
import (
"log"
"os"
"path/filepath"
"time"
)
// 日志轮转器
type LogRotator struct {
BaseName string
MaxSize int64 // 最大文件大小,单位:字节
MaxDays int // 最大保留天数
}
func (lr *LogRotator) rotate() {
// 获取当前时间
now := time.Now()
// 创建新的日志文件
newFileName := filepath.Join(filepath.Dir(lr.BaseName),
filepath.Base(lr.BaseName)+"."+now.Format("20060102")+".log")
// 如果文件存在,重命名旧文件
if _, err := os.Stat(lr.BaseName); err == nil {
os.Rename(lr.BaseName, newFileName)
}
}
func (lr *LogRotator) CheckAndRotate() {
// 检查文件大小
if fi, err := os.Stat(lr.BaseName); err == nil {
if fi.Size() > lr.MaxSize {
lr.rotate()
}
}
// 检查文件年龄
now := time.Now()
// 删除超过最大保留天数的旧日志
for i := 0; i < lr.MaxDays; i++ {
date := now.AddDate(0, 0, -i).Format("20060102")
oldFile := filepath.Join(filepath.Dir(lr.BaseName),
filepath.Base(lr.BaseName)+"."+date+".log")
if _, err := os.Stat(oldFile); err == nil {
os.Remove(oldFile)
}
}
}
// 使用示例
func main() {
rotator := LogRotator{
BaseName: "app.log",
MaxSize: 100 * 1024 * 1024, // 100MB
MaxDays: 30,
}
// 初始化日志
file, err := os.OpenFile(rotator.BaseName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal("Failed to open log file:", err)
}
// 检查并轮转日志
rotator.CheckAndRotate()
// 设置日志输出
log.SetOutput(file)
// 记录日志
log.Println("Application started")
}
十、总结
Go语言提供了丰富的日志记录工具,从简单的标准库log到高性能的zap和log/slog,开发者可以根据项目需求选择合适的日志库。
关键建议:
- 选择适合的库:小型项目用标准库log,需要结构化日志用logrus或zap,Go 1.21+项目用log/slog
- 合理设置日志级别:避免生产环境使用DEBUG级别
- 使用结构化日志:JSON格式便于后续分析
- 实施日志轮转:避免日志文件过大
- 保护敏感信息:不要在日志中记录敏感数据
- 模块化日志管理:为每个模块创建独立logger
通过合理选择和配置日志系统,开发者可以显著提高应用的可维护性和故障排查能力,确保应用在生产环境中稳定运行。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

