logrus默认不支持按级别分割文件,因其Hook机制仅事件驱动且无内置级别过滤逻辑;需自定义Hook在Fire方法中根据entry.Level选择对应*os.File写入,并注意并发安全、文件句柄管理及退出时关闭。

为什么 logrus 默认不支持按级别分割文件
logrus 本身没有内置的「按日志级别自动写入不同文件」能力。它的 Hook 机制是事件驱动的,但默认 FileHook(如 rotatelogs 配合的)只管输出目标,不管日志级别过滤逻辑。你直接把 logrus.Info 和 logrus.Error 都塞进同一个 Hook,它们就会混在一起——这不是 bug,是设计如此。
真正起作用的是你自己写的 Fire 方法:必须在里面判断 entry.Level,再决定写到哪个 *os.File。
- 别依赖第三方 Hook 库声称“支持 level 分割”,多数只是包装了
io.MultiWriter或做了简单路由,没做并发安全的文件句柄管理 - 多个级别共用一个
os.File句柄 +sync.Mutex是可行路径,但要注意os.OpenFile的os.O_APPEND模式在多 goroutine 下是安全的,不用额外锁写操作 - 错误日志单独文件时,建议用
os.O_CREATE | os.O_WRONLY | os.O_APPEND,避免误覆盖
在 Echo 中注册多级 Logrus 实例的正确姿势
Echo 的 Logger 接口只接受一个 echo.Logger,它底层调用的是 log.SetOutput 那套。所以你不能直接把多个 logrus.Logger 塞进去——得统一入口,再按需分发。
推荐做法:保留一个主 *logrus.Logger 实例作为 Echo 的 HTTPErrorHandler 和中间件日志源,再通过自定义 Hook 把不同级别路由到不同文件。
立即学习“go语言免费学习笔记(深入)”;
- 不要为每个级别 new 一个
logrus.Logger,否则 Level 判断会失效(因为Entry.Level是在写入前才确定的) - Echo 的
e.Logger.SetLevel()控制的是框架自身日志(如启动、panic),不影响你业务里用的logrus实例 - 若要用 Echo 的
logger.With().Info()等方法,需用echo.NewHTTPLogger包装logrus,但此时仍要自己实现Write方法来拆分级别
LevelFilterHook 的关键实现细节
你需要一个实现了 logrus.Hook 接口的结构体,内部持有多个 *os.File,并在 Fire 方法中根据 entry.Level 写入对应文件:
type LevelFilterHook struct {
DebugFile *os.File
InfoFile *os.File
ErrorFile *os.File
}
func (h *LevelFilterHook) Fire(entry *logrus.Entry) error {
var file *os.File
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
file = h.DebugFile
case logrus.InfoLevel, logrus.WarnLevel:
file = h.InfoFile
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
file = h.ErrorFile
default:
file = h.InfoFile
}
if file == nil {
return nil
}
line, err := entry.String()
if err != nil {
return err
}
_, err = file.WriteString(line)
return err
}
func (h *LevelFilterHook) Levels() []logrus.Level {
return logrus.AllLevels
}
- 务必在程序退出前调用
file.Close(),否则日志可能丢失或文件句柄泄漏 - 如果配合
rotatelogs,不要直接传*rotatelogs.RotateLogs给File字段——它不是*os.File,得用rotatelogs.Writer封装 -
entry.String()已包含换行符,WriteString后不要再加\n,否则空行爆炸
实际部署时容易被忽略的三个点
本地跑通不等于上线可用。以下三点在容器或 systemd 环境下极易出问题:
- 日志目录权限:Go 进程用户(如
www-data)必须对日志路径有write+execute权限,否则open /var/log/myapp/error.log: permission denied - 文件句柄数限制:
ulimit -n默认常为 1024,开 5 个级别文件 + rotatelogs 轮转 + Echo 自身日志,很容易 hit limit,表现为too many open files - rotatelogs 的
WithLinkName在多实例部署时会冲突,建议关闭软链,用时间戳命名,或改用lumberjack(它内部带锁且更轻量)
轮转策略和文件名格式比想象中更影响运维排查效率。比如 error-2024-06-15.log 看起来清晰,但当日志量大时,grep 所有 error 文件就得写 grep "timeout" error-*.log;而用 app-%Y-%m-%d-%H.log 加 level=error 字段,反而更利于日志采集器归类。


















