Go中需手动用time.Now().Format()生成时间戳,格式必须严格匹配“2006-01-02 15:04:05”;追加写入须用os.OpenFile配O_APPEND|O_WRONLY|O_CREATE;log包时间戳不可自定义格式及时区;并发写入需用channel或mutex保障顺序与一致性。

写入文件前用 time.Now() 生成时间戳字符串
Go 没有“自动”加时间戳的内置文件写入函数,必须手动拼接。最直接的方式是在调用 os.WriteFile 或 io.WriteString 前,用 time.Now().Format() 生成带格式的时间字符串。
注意:Format() 的 layout 参数不是任意字符串,必须严格匹配 Go 的固定参考时间 "2006-01-02 15:04:05"(这是唯一能被正确解析的模板)。写错 layout 会导致返回空字符串或意外结果。
- 常用格式示例:
time.Now().Format("2006-01-02_15-04-05")→"2024-05-22_14-30-25" - 避免用
"YYYY-MM-DD"这类常见但 Go 不识别的写法,它会输出字面量"YYYY-MM-DD" - 如果要写入日志类内容,建议把时间戳放在行首,例如:
fmt.Sprintf("[%s] %s\n", t.Format("15:04:05"), msg)
用 os.OpenFile 追加写入时注意文件打开模式
如果目标是往已有文件末尾添加带时间戳的新行(比如日志),不能用 os.WriteFile——它会覆盖整个文件。必须用 os.OpenFile 配合 os.O_APPEND | os.O_WRONLY | os.O_CREATE 标志。
容易漏掉 os.O_CREATE:当文件不存在时,缺少该标志会返回 "no such file or directory" 错误,而不是自动创建。
- 正确打开方式:
f, err := os.OpenFile("log.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) - 写入后记得
defer f.Close(),否则文件句柄泄漏,多次运行后可能报"too many open files" - 用
fmt.Fprintf(f, "[%s] %s\n", time.Now().Format("2006-01-02T15:04:05Z"), "task done")直接写入带时间戳的行
log 包默认就带时间戳,但格式和位置不可控
标准库 log 包在初始化时可通过 log.SetFlags() 启用时间戳,例如:log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)。但它把时间戳放在每行开头,且格式固定(如 2024/05/22 14:30:25.123),无法自定义分隔符或时区。
如果你需要 ISO 8601 格式(2024-05-22T14:30:25+08:00)或精确到纳秒,或者想把时间戳嵌在消息中间(如 "processed at 2024-05-22T14:30:25Z"),log 包就不适用了,得回退到手动拼接。
- 启用默认时间戳:
log.SetFlags(log.LstdFlags)(等价于Ldate | Ltime) - 禁用时间戳:
log.SetFlags(0),之后全靠自己拼 -
log写入的是os.Stderr,重定向到文件需额外传入os.File句柄
并发写入多个 goroutine 时,时间戳可能重复或乱序
如果多个 goroutine 同时调用同一文件的写入逻辑,即使每次调用都用 time.Now(),仍可能出现两行时间戳完全相同(纳秒级精度下极小概率),更严重的是写入顺序与执行顺序不一致(因系统调度和 I/O 缓冲)。
这不是时间戳生成的问题,而是文件 I/O 的并发安全问题。解决方法不是加锁整个写入流程(性能差),而是用带缓冲的 channel + 单 goroutine 消费者模型统一处理写入请求。
- 简单场景可用
sync.Mutex包裹f.Write()调用,但只适合低频写入 - 高频日志推荐用现成库如
zap或zerolog,它们内部已处理时间戳生成、格式化和并发写入 - 手动实现时,确保
time.Now()在加锁区域内调用,避免“获取时间→等待锁→写入”导致时间戳和实际写入时间偏差过大
真正麻烦的不是怎么加时间戳,而是决定它该出现在哪、用什么格式、是否要对齐、以及多线程下怎么保证可读性——这些比调用一次 Format() 花的时间多得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











