用os.openfile而非os.create写日志,因其支持追加模式避免覆盖旧数据;需组合os.o_append|os.o_create|os.o_wronly标志并设权限0644;推荐fmt.fprintf格式化写入,字段用\t分隔、固定时间格式、结尾加\n;并发写须加sync.mutex锁;应实现按周/大小轮转日志并安全清理。

为什么用 os.OpenFile 而不是 os.Create 写日志
因为考勤日志需要追加写入,每天新增记录不能覆盖旧数据。os.Create 每次都会清空文件重来,而 os.OpenFile 可以精确控制打开模式。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 固定用
os.O_APPEND | os.O_CREATE | os.O_WRONLY三个 flag 组合,缺一不可 - 避免只写
os.O_APPEND—— 如果文件不存在会报No such file or directory - 权限设为
0644,别用0600,否则排查时连cat都打不开 - 示例:
f, err := os.OpenFile("attendance.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
fmt.Fprintf 格式化写入比 json.Marshal 更适合考勤日志
考勤记录是结构化但非强 schema 的文本流:时间、姓名、状态(打卡/请假/缺勤)、备注。JSON 会引入多余引号、转义和换行,增加解析负担且不易人工核对。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
fmt.Fprintf(f, "%s\t%s\t%s\t%s\n", time.Now().Format("2006-01-02 15:04:05"), name, status, note) - 字段间用
\t分隔,比逗号更安全(姓名或备注里可能含逗号) - 日期格式必须写死为
"2006-01-02 15:04:05",Go 的时间格式化是魔数,写错会静默失败 - 每条记录末尾务必加
\n,否则所有日志挤在一行,tail -f看不到实时刷新
不加锁直接并发写日志会丢记录
多个 goroutine 同时调用 fmt.Fprintf 写同一个 *os.File,会出现内容交错、截断甚至 panic —— 因为 Write 不是原子操作,底层 write(2) 调用可能被抢占。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 全局声明一个
var logMu sync.Mutex,每次写前logMu.Lock(),写完logMu.Unlock() - 不要试图用
bufio.Writer缓冲后批量写——缓冲区在多 goroutine 下仍需锁,且崩溃时缓存丢失风险更高 - 如果日志量极大(>100 条/秒),再考虑用 channel + 单 goroutine 消费的模型;小系统直接锁更简单可靠
- 错误现象示例:某次打卡记录变成
"2024-05-20 09:01:23\t张三\t打卡\t(缺结尾换行和备注),下一行开头是请假\t事假
日志文件不轮转,几个月后磁盘爆掉
考勤系统跑一年,attendance.log 很容易超 1GB。不处理的话,ls -lh 都卡,grep 查半天,备份也慢。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每周日零点自动切新文件,命名如
attendance-20240519.log(用上周日日期) - 切文件时先
f.Close(),再os.Rename,最后用os.OpenFile新建 - 切之前检查原文件大小,超过 100MB 就强制切,别死守“每周”——防止某天批量补录把单文件撑爆
- 保留最近 12 个文件,老文件
os.Remove,别用rm -rf脚本混搭,Go 自己删更可控
最常被忽略的一点:切文件逻辑必须和写日志共用同一把锁,否则切到一半被另一个 goroutine 写入,新旧文件都会损坏。










