在 Go 中使用多个 goroutine 并发消费同一个 channel,这本是日常开发里的常规操作,但稍不留神就容易翻车——尤其是跟共享状态打交道的时候。你碰到的那个怪现象:“所有日志只写进了第四个文件”,真相并不是 channel 分发逻辑出了岔子,而是对 Go 标准库 log 包的使用踩了坑

问题出在哪?log.SetOutput()log.SetFlags() 这两个方法,修改的是全局、包级的默认 logger(也就是 log.Default() 返回的那个实例)。当 4 个 LogWorker.Work() 方法依次跑起来之后,每个方法都会调一次 log.SetOutput(...),把全局 logger 的输出目标换成自己的 lumberjack.Logger。最后执行的那个 goroutine(通常是第 4 个)的设置会留下来,覆盖掉前面三个。其余 goroutine 虽然也在老老实实地调 log.Println(...),但实际输出的目标早就被最后那个覆盖成了同一个——第四个文件。这就是为什么你只看到一个日志文件里有内容,其余三个文件干干净净。

✅ 正确的解决方案其实不复杂:给每个 goroutine 创建一个专属的 *log.Logger 实例,别再动全局 logger 了。把 LogWorker.Work 方法改成下面这样:

func (lw *LogWorker) Work(evChannel <-chan Event) {
    fmt.Printf("LogWorker started: %s\n", lw.FileName)
    // ✅ 为本 goroutine 创建独立 logger
    lg := log.New(&lumberjack.Logger{
        Filename:   lw.FileName,
        MaxSize:    int64(lw.MaxSize) * 1024 * 1024, // 注意:lumberjack.MaxSize 单位是字节
        MaxBackups: lw.MaxBackups,
        MaxAge:     lw.MaxAge,
    }, "", log.LstdFlags) // 可选:加个时间戳之类的标志
    // ✅ 使用 range 遍历 channel,优雅退出
    for event := range evChannel {
        lg.Println(Csv(event))
    }
}

这段代码有四个关键改动值得注意:

⚠️ 还有几个细节需要留意:

经过上面这番改造,4 个 LogWorker 就能真正并行、独立地消费 evChannel,各自把日志写到指定的文件里(比如 event_0event_1……),实现负载分散和日志隔离,再也不会互相干扰了。

本文转载于:https://www.php.cn/faq/2342471.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。