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 (标准库)logruszaplog/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,开发者可以根据项目需求选择合适的日志库。

关键建议

  1. 选择适合的库:小型项目用标准库log,需要结构化日志用logrus或zap,Go 1.21+项目用log/slog
  2. 合理设置日志级别:避免生产环境使用DEBUG级别
  3. 使用结构化日志:JSON格式便于后续分析
  4. 实施日志轮转:避免日志文件过大
  5. 保护敏感信息:不要在日志中记录敏感数据
  6. 模块化日志管理:为每个模块创建独立logger

通过合理选择和配置日志系统,开发者可以显著提高应用的可维护性和故障排查能力,确保应用在生产环境中稳定运行。