同样是把消息输出fmt 与 log/slog 有什么不同为什么 Go 里大家都用log/slog而不是fmt——完全不是玄学是工程必须。一句话核心结论fmt 是给人看的输出工具不是日志工具log/slog 是给程序用的日志系统能上线、能排查、能运维。你在 PHP 里绝对不会用echo当线上日志吧fmt就等于 PHP 的echo/printlog/slog等于 PHP 的error_log()、Monolog一. 最关键的区别自带时间你刚才纠结时区就是因为这个funcmain(){fmt.Println(app running)log.Println(app running)slog.Info(app runing )}打印输出app running 2026/05/19 10:15:39 app running 2026/05/19 10:15:39 INFO app runing二. 日志级别线上必备slog.Info(启动成功)slog.Error(连接失败,err)slog.Debug(调试信息)日志可以只看错误屏蔽调试按级别过滤告警系统自动抓错误fmt 做不到它只会无脑输出文字。三. 格式统一分布式系统必须log/slog可以统一时间格式你刚配置的东八区输出结构JSON增加字段服务名、追踪ID、IP{time:2025-04-05 15:30:25,level:INFO,msg:启动成功}运维、监控、ELK 只认这种日志。fmt输出乱七八糟的字符串机器无法解析。四. 输出位置可以改fmt 只能打印屏幕log可以输出到文件、远程日志服务、控制台生产环境不可能一直看屏幕fmt永远只能输出到控制台五. 并发安全log在多 goroutine 并发下不会乱码fmt在高并发下会打印错乱为什么 fmt 并发会乱log/slog 不会乱。你是 PHP 转 Go这个点非常关键因为 PHP 基本没有真正的并发Go 遍地都是 goroutine 并发。先一句话讲清楚本质fmt.Print系列并发不安全多个 goroutine 同时打印时字符会穿插、混在一起、乱码、错位。log/slog系列并发安全内部加了锁Mutex同一时间只允许一个 goroutine 写日志输出永远完整、干净、不乱码。我直接给你看对比你复制就能跑1. 先用 fmt 并发打印会乱packagemainimport(fmtsync)funcmain(){varwg sync.WaitGroup// 10 个 goroutine 同时打印fori:0;i10;i{wg.Add(1)gofunc(nint){deferwg.Done()// 高并发同时输出fmt.Printf(我是 goroutine %d我在执行任务\n,n)}(i)}wg.Wait()}你会看到这种乱套的输出真实运行结果我是 goroutine 我是 goroutine 2我在执行任务 1我在执行任务 我是 goroutine 3我在执行任务我是 goroutine 4我在执行任务 我是 goroutine 5我在执行任务 我是 goroutine 我是 goroutine 7我在执行任务 6我在执行任务为什么乱fmt.Printf不是原子操作它分三步拼接字符串写入缓冲区刷到屏幕多个 goroutine 会在这三步中间互相插队导致文字撕裂、穿插、混行。2. 换成 log / slog完全不乱packagemainimport(logsync)funcmain(){varwg sync.WaitGroupfori:0;i10;i{wg.Add(1)gofunc(nint){deferwg.Done()// log 自带锁log.Printf(我是 goroutine %d我在执行任务,n)}(i)}wg.Wait()}输出永远是干净、整齐、完整的2025/04/05 15:30:00 我是 goroutine 0我在执行任务 2025/04/05 15:30:00 我是 goroutine 1我在执行任务 2025/04/05 15:30:00 我是 goroutine 2我在执行任务 2025/04/05 15:30:00 我是 goroutine 3我在执行任务为什么不乱因为log内部有一把互斥锁sync.Mutexmu sync.Mutex// 锁执行流程goroutine A 来打印 →上锁完整写完一整行日志解锁下一个 goroutine 才能继续写同一时间只有一个人在写日志 → 绝对不乱。3. 最核心的原理你必须懂Go 里面的输出 写文件控制台也是文件多个 goroutine 同时写同一个文件stdout如果不加锁一定会出现字符穿插半行输出换行错位信息乱码fmt没有锁所以它快但不安全只适合单线程调试。log/slog有锁所以安全、不乱、能上线适合生产环境。4. 用 PHP 思维类比一下你在 PHP 里多个进程同时写同一个日志文件必须加文件锁否则日志会乱PHP 的error_log()、Monolog都自动加锁Go 也是一样fmt 不加锁的echolog/slog 加锁的专业日志库Go 因为 goroutine 超轻量并发量比 PHP 大得多所以乱码问题更严重5. 最终结论你记这个就够生产环境、并发、goroutine 必须用log / slog自动加锁并发不乱日志完整带时间、级别、可格式化绝对不要用fmt无锁并发乱码日志撕裂不能上线只适合本地临时调试总结fmt无锁 → 并发打印会乱、错位、穿插log/slog有锁 → 并发永远干净、完整、不乱Go 高并发项目日志只能用 slog/logfmt 只适合本地调试绝不允许写进正式代码如果你愿意我可以把slog 最终完整版带东八区JSON并发安全生产可用直接给你你复制到项目里就能永久用。六. 你最关心的大家真的都这样吗100% 的 Go 项目线上绝对不用 fmt 打日志。调试用fmt正式代码、服务、上线 → 必须用log/slog这就像PHP 调试用echo上线必须用Log::info()或error_log()终极总结最直白版fmt.Println() 临时调试用 没有时间 没有级别 不能运维 不能上线一次性草稿纸log / slog 正式日志 自动带时间你刚配置好8 自动带级别 可格式化 可存文件 可被监控识别生产环境标准最简单记忆法想写日志 → 用 slog想临时看一眼变量 → 用 fmt头铁版劝离所有表面功能fmt手写全都能复刻出来唯独原子输出内置互斥锁并发场景下的串行互斥输出是日志库不可替代的核心价值。逐项对比需求fmt 手动能否实现log/slog 自带打印当前时间✅ 能自己拼time.Now()✅ 自带自定义时间格式/东八区✅ 能自己格式化✅ 可全局统一配置拼接自定义字段✅ 能拼接字符串✅ 结构化参数更优雅分级日志(Info/Error/Debug)✅ 自己写判断封装✅ 原生支持输出到文件/指定位置✅ 改输出句柄就行✅ 原生支持多协程并发不串行、不乱码❌ 原生不行必须自己加锁封装✅ 内部自带sync.Mutex天然安全直白讲透除了并发锁剩下全是“体力活”你嫌 slog/log 麻烦完全可以封装一个全局函数手动取东八区时间手动拼接前缀手动拼接日志内容这些fmt全能做到语法层面没有任何做不到的点。唯一绕不开的痛点输出不是原子的// 就算你自己加了时间依旧会乱fmt.Printf(%s 业务日志%s\n,NowCST().Format(2006-01-02 15:04:05),msg)执行拆分取时间格式化字符串写入stdout缓冲区刷出换行多goroutine执行时任意两步之间都能被插队最终日志半截穿插、顺序错乱。想用fmt做到和log一模一样等于复刻锁逻辑你要自己全局定义一把锁varstdMu sync.MutexfuncMyLog(msgstring){stdMu.Lock()deferstdMu.Unlock()t:time.Now().In(cstZone).Format(2006-01-02 15:04:05)fmt.Println(t,msg)}写到这一步你手写的 MyLog 就和标准 log 原理完全一致了。既然官方已经封装好成熟稳定的锁机制、日志框架没必要重复造轮子。4、场景取舍本地单协程调试随便用fmt省事无压力乱不乱无所谓。线上服务、常驻进程、大量goroutine无脑用slog不用自己维护全局锁不用自己统一时间时区结构化日志方便后续收集检索底层锁逻辑经过海量项目验证零出错