
本文介绍如何使用 Go 标准库 log 包结合 io.MultiWriter,将日志同时输出到终端(INFO 级别)和文件(DEBUG 级别),并支持时间戳与级别标识,无需第三方日志框架即可灵活定制。
本文介绍如何使用 go 标准库 `log` 包结合 `io.multiwriter`,将日志同时输出到终端(info 级别)和文件(debug 级别),并支持时间戳与级别标识,无需第三方日志框架即可灵活定制。
在 Go 开发中,常需将不同级别的日志分流至不同目的地:例如调试信息(DEBUG)写入文件便于追踪,而关键提示(INFO 及以上)实时显示在终端。标准 log 包本身不支持按级别分发日志,但可通过组合 I/O 接口实现“多路输出”——核心在于 io.MultiWriter,它能将一次写操作透明地复制到多个 io.Writer(如 os.Stdout 和 *os.File)。
然而需注意:io.MultiWriter 仅解决“输出目标合并”,不区分日志级别。若要实现「终端只显示 INFO+,文件记录 DEBUG+」,必须自行封装日志器。以下是推荐的轻量级实现方案:
✅ 步骤一:创建带级别过滤的双输出日志器
package main
import (
"io"
"log"
"os"
"time"
)
// LevelWriter 封装 Writer 并关联最小日志级别
type LevelWriter struct {
writer io.Writer
level int // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR
}
func (lw *LevelWriter) Write(p []byte) (n int, err error) {
return lw.writer.Write(p)
}
// 自定义日志器(支持级别路由)
type MultiLevelLogger struct {
debugLog *log.Logger
infoLog *log.Logger
}
func NewMultiLevelLogger(file *os.File) *MultiLevelLogger {
// 文件输出:含时间戳 + DEBUG 级别前缀
fileWriter := io.MultiWriter(file)
debugLog := log.New(fileWriter, "[DEBU] ", log.LstdFlags)
// 控制台输出:仅 INFO 及以上,简洁格式
stdoutWriter := io.MultiWriter(os.Stdout)
infoLog := log.New(stdoutWriter, "", 0) // 不加时间戳,避免重复
return &MultiLevelLogger{
debugLog: debugLog,
infoLog: infoLog,
}
}
func (l *MultiLevelLogger) Debug(v ...interface{}) {
l.debugLog.SetPrefix("[DEBU] ")
l.debugLog.Println(append([]interface{}{time.Now().Format("2006-01-02T15:04:05Z07:00")}, v...)...)
}
func (l *MultiLevelLogger) Infof(format string, v ...interface{}) {
l.infoLog.Printf("[INFO] "+format, v...)
}
✅ 步骤二:使用示例
func main() {
// 创建日志文件
f, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
defer f.Close()
logger := NewMultiLevelLogger(f)
// 模拟业务逻辑
logger.Debug("Entering some func")
result := "Successful"
logger.Infof("Result was: %s", result)
}
运行后:
- 终端输出:[INFO] Result was: Successful
-
文件内容:
2024-06-15T10:30:45+08:00 [DEBU] Entering some func 2024-06-15T10:30:45+08:00 [INFO] Result was: Successful
⚠️ 注意事项
- io.MultiWriter 是零拷贝转发,性能高效,但无法做级别过滤——所有写入均广播到所有目标。真正按级别分流必须手动控制 Write() 或封装 Logger。
- 若需更高级功能(异步写入、轮转日志、JSON 格式等),建议选用成熟库如 zerolog 或 zap,它们原生支持多 Writer + 级别过滤。
- 标准 log 的 SetFlags() 对所有 Logger 实例独立生效,务必为不同输出配置专属前缀与标志。
通过此方案,你无需引入重量级依赖,即可精准控制日志流向与格式,兼顾开发调试效率与生产可观测性。











