
本文介绍如何在 Go 中实现灵活、线程安全的日志系统,涵盖标准库 log 的高级用法、多级日志设计、文件输出配置,并推荐生产就绪的第三方库(如 logrus),兼顾并发安全与工程可维护性。
本文介绍如何在 go 中实现灵活、线程安全的日志系统,涵盖标准库 `log` 的高级用法、多级日志设计、文件输出配置,并推荐生产就绪的第三方库(如 logrus),兼顾并发安全与工程可维护性。
Go 标准库的 log 包远比表面更强大——它并非“简陋”,而是以组合式设计提供高度可定制性。核心在于:所有日志行为均由 io.Writer 驱动,且支持实例化独立 logger。这意味着你无需依赖第三方库即可实现类 log4j 的基础能力,如多级别、多输出目标、前缀控制等。
✅ 基于标准库构建可配置 Logger
最轻量的方式是封装多个 *log.Logger 实例,按日志级别分离:
package main
import (
"log"
"os"
"time"
)
type Logger struct {
debug *log.Logger
info *log.Logger
warn *log.Logger
err *log.Logger
}
func NewLogger(file *os.File) *Logger {
// 共享同一 writer,确保原子写入(需注意:文件本身需支持并发写,或加锁)
prefix := ""
flag := log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile
return &Logger{
debug: log.New(file, "[DEBUG] ", flag),
info: log.New(file, "[INFO] ", flag),
warn: log.New(file, "[WARN] ", flag),
err: log.New(file, "[ERROR] ", flag),
}
}
func (l *Logger) Debug(v ...interface{}) {
l.debug.Output(2, fmt.Sprint(v...)) // 跳过封装层调用栈
}
// 同理实现 Info/Warn/Error 方法...⚠️ 注意:若将 os.File 直接作为 io.Writer 传入多个 logger,Go 标准库的 log 是线程安全的(内部使用 sync.Mutex),但底层文件 I/O 并发写入仍可能引发内容交错(尤其未开启 O_APPEND 或未同步)。推荐做法是:
- 使用 os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 确保追加写;
- 或统一通过单个 logger 实例 + level 字段实现(更推荐);
- 生产环境建议直接选用成熟库(见下文)。
? 文件日志落地示例
以下代码演示如何将日志持久化到带日期滚动的文件中(简易版):
func setupLogFile() *os.File {
filename := fmt.Sprintf("app-%s.log", time.Now().Format("2006-01-02"))
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
}
return f
}
func main() {
logFile := setupLogFile()
defer logFile.Close()
logger := log.New(logFile,
"[APP] ",
log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile,
)
logger.Println("Application started")
logger.Printf("Config loaded: %+v", config)
}? 推荐生产级方案:Logrus(现为 github.com/sirupsen/logrus)
尽管标准库足够灵活,但 logrus 提供了开箱即用的结构化日志、字段注入、Hook 机制(如自动发送错误到 Slack)、多格式输出(JSON/Text)及完善的 Level 支持:
import log "github.com/sirupsen/logrus"
func init() {
log.SetLevel(log.InfoLevel)
log.SetOutput(&lumberjack.Logger{
Filename: "logs/app.log",
MaxSize: 50, // MB
MaxBackups: 3,
MaxAge: 28, // days
Compress: true,
})
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
}
func main() {
log.WithFields(log.Fields{
"service": "api",
"user_id": 123,
}).Info("User logged in")
}✅ 优势总结:
- 天然并发安全:所有方法均为 goroutine-safe;
- 统一实例管理:全局单例(log.WithXXX() 返回新上下文,不破坏原 logger);
- 生态完善:支持 lumberjack 滚动、syslog、kafka 等多种 Hook;
- 结构化友好:便于 ELK / Loki 等日志平台采集分析。
? 最佳实践总结
- 不要每个函数都新建 logger:创建成本低,但共享实例更利于上下文追踪与性能;
- 避免混用 log.Printf 和自定义 logger:统一入口便于后期替换或增强;
- 敏感信息脱敏:日志中勿打印密码、token、完整请求体;
- 异步写入谨慎使用:除非有明确性能瓶颈,否则优先保证日志不丢失(同步写入更可靠);
- 启动时初始化日志:在 main() 早期完成配置,确保初始化错误也能被记录。
选择取决于项目阶段:学习/小工具用标准库足矣;中大型服务请直接采用 logrus 或其现代替代品(如 zerolog、zap),它们已在千万级 QPS 场景中久经考验。


















