
本文介绍如何在 go 中使用 logrus 等主流日志库,将不同日志级别(如 debug、info)分别输出到控制台和文件,并自动添加时间戳,实现灵活、可维护的多目标日志路由。
本文介绍如何在 go 中使用 logrus 等主流日志库,将不同日志级别(如 debug、info)分别输出到控制台和文件,并自动添加时间戳,实现灵活、可维护的多目标日志路由。
在 Go 开发中,仅靠标准库 log 或简单使用 io.MultiWriter 并不能满足「按级别分流」的核心需求——例如:Debug 日志写入文件,Info 及以上日志同时输出到终端和文件。虽然 io.MultiWriter 可实现“同一日志写入多个目的地”,但它无法区分级别,所有日志都会无差别地同步到所有输出端,这与题设需求(log.Debug() 仅进文件,log.Info() 同时进终端+文件)存在本质差异。
真正可行的方案是借助结构化日志库(如 logrus 或 zerolog)的 Hook(钩子)机制 或 自定义 Writer + Level Filter。以下是基于 logrus 的推荐实现:
✅ 推荐方案:使用 logrus + 自定义 Hook 实现分级多输出
package main
import (
"io"
"os"
"time"
"github.com/sirupsen/logrus"
)
// LevelFilterWriter 根据日志级别过滤并写入指定 io.Writer
type LevelFilterWriter struct {
writer io.Writer
level logrus.Level
}
func (w *LevelFilterWriter) Write(p []byte) (n int, err error) {
return w.writer.Write(p)
}
// NewFilteredHook 创建一个只处理指定级别及以上日志的 Hook
type FilteredHook struct {
writer io.Writer
level logrus.Level
}
func (h *FilteredHook) Fire(entry *logrus.Entry) error {
if entry.Level >= h.level {
line, err := entry.String()
if err != nil {
return err
}
_, err = h.writer.Write([]byte(line))
return err
}
return nil
}
func (h *FilteredHook) Levels() []logrus.Level {
// 返回所有可能触发该 Hook 的级别(logrus 要求)
allLevels := make([]logrus.Level, 0, 6)
for l := logrus.PanicLevel; l <h3>⚠️ 注意事项与最佳实践</h3>
- 避免 io.MultiWriter 的误用:它适用于「完全镜像」场景(如所有日志都发到 stdout + file),但无法实现题目要求的「Debug 仅存盘、Info 同时展示」,强行使用会导致敏感调试信息泄露到终端。
- 时间戳格式统一:通过 TextFormatter{FullTimestamp: true} 确保所有输出含 RFC3339 格式时间,便于日志分析。
- 文件句柄管理:生产环境建议使用 lumberjack 等轮转库(如 github.com/natefinch/lumberjack)避免单日志文件无限增长。
- 性能考量:Hook 是同步执行的,高并发下若文件 I/O 慢,可能阻塞主逻辑;关键服务可考虑异步 Hook(需自行封装 goroutine + channel)。
✅ 总结
Go 中实现「多输出 + 多级别」日志,核心在于解耦日志路由逻辑与输出行为。logrus 的 Hook 机制为此提供了优雅、可扩展的解决方案。相比手动拼接或魔改 Writer,Hook 更符合关注点分离原则,也易于单元测试和复用。对于新项目,也可评估 zerolog(更轻量、零分配)或 zap(高性能结构化日志),它们均原生支持多写入器与级别过滤。
最终目标不是“让日志去多个地方”,而是“让正确的日志,在正确的时间,以正确的方式,抵达正确的观察者”——这才是可观测性的起点。











