
go 中调用 os.openfile 时若仅传入 os.o_append 而未指定读写模式,会导致“bad file descriptor”错误;必须显式添加 os.o_wronly(或 os.o_rdwr)标志才能获得可写句柄。
go 中调用 os.openfile 时若仅传入 os.o_append 而未指定读写模式,会导致“bad file descriptor”错误;必须显式添加 os.o_wronly(或 os.o_rdwr)标志才能获得可写句柄。
在 Go 程序中以 os.O_APPEND 模式打开文件进行日志写入时,一个常见却容易被忽略的错误是:仅指定 os.O_APPEND,却未声明访问权限(如只写或读写)。这会导致底层系统调用 open() 失败,返回 EBADF(Bad file descriptor),因为 POSIX 标准强制要求 flags 参数必须包含且仅包含一个访问模式标志:O_RDONLY、O_WRONLY 或 O_RDWR。
你原始代码的问题就在此处:
f, err := os.OpenFile("./log.log", os.O_APPEND, os.ModeAppend) // ❌ 错误:缺少 O_WRONLYos.O_APPEND 本身不隐含写权限——它只是一个行为修饰符(表示每次写入前自动 seek 到文件末尾),而非访问模式。Go 的 os.OpenFile 将参数透传给系统 open() 系统调用,而 Linux 内核会拒绝不含有效访问模式的 flags(默认为 0,即 O_RDONLY),从而导致句柄无效。
✅ 正确写法是组合使用 os.O_APPEND | os.O_WRONLY(推荐)或 os.O_APPEND | os.O_RDWR:
f, err := os.OpenFile("./log.log", os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
log.Fatalf("failed to open log file: %v", err)
}
defer f.Close() // 注意:此处应 defer,避免 panic 前未关闭
logTime := time.Now().Format(time.RFC3339)
_, err = f.WriteString(logTime + " - " + msg + "\n")
if err != nil {
log.Printf("failed to write log: %v", err)
}⚠️ 注意事项:
- 权限参数(第三个参数)应使用 0666(八进制字面量),而非 os.ModeAppend(后者是用于 os.MkdirAll 或 os.Chmod 的文件属性标志,与打开模式无关);
- os.ModeAppend 是 os.FileMode 类型的常量,不能作为 os.OpenFile 的 flag 使用,混用将导致静默逻辑错误;
- 即使使用了 sync.Mutex,也不应频繁反复 OpenFile + Close —— 高频日志场景建议复用文件句柄,并配合 bufio.Writer 提升性能;
- 若需多 goroutine 安全写入,推荐使用 log.SetOutput(f) 配合标准库 log,或封装带锁的 io.WriteCloser,而非手动管理 fd。
总结:os.O_APPEND 必须与 os.O_WRONLY(或 os.O_RDWR)按位或组合使用,这是系统调用契约,非 Go 特有行为。理解这一底层约束,能从根本上避免 “bad file descriptor” 类错误。


















