
Go 默认日志输出到 stderr,而 daemonize 会关闭标准流,导致日志丢失;需通过 log.SetOutput() 指向文件,或改用更现代的进程管理方式(如 systemd)替代 daemonize。
go 默认日志输出到 stderr,而 `daemonize` 会关闭标准流,导致日志丢失;需通过 `log.setoutput()` 指向文件,或改用更现代的进程管理方式(如 systemd)替代 `daemonize`。
在 Go 中开发 Web 服务(如 tracking_api)后,常需将其作为守护进程(daemon)长期运行。但直接使用 daemonize 工具配合 shell 重定向(如 1>> log)往往无法捕获日志——这是因为 Go 的 log 包默认写入 os.Stderr(而非 stdout),而 daemonize 在启动子进程时会主动关闭 stdin、stdout 和 stderr 三个标准文件描述符,导致所有未显式重定向的日志被静默丢弃。
✅ 正确做法一:在 Go 代码中接管日志输出
推荐在程序启动时,将日志输出重定向至磁盘文件:
package main
import (
"log"
"os"
)
func main() {
// 打开日志文件(追加模式)
f, err := os.OpenFile("/var/www/repo/bin/tracking_api.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatalf("无法打开日志文件: %v", err)
}
defer f.Close()
// 将 log 包的全局输出设为该文件
log.SetOutput(f)
// 后续所有 log.Print/log.Printf/log.Fatal 都将写入文件
log.Println("服务已启动,监听中...")
// 启动 HTTP 服务器示例
// http.ListenAndServe(":8080", nil)
}
⚠️ 注意:
defer f.Close()不应放在main()开头,否则日志文件会提前关闭;应确保f在整个生命周期中保持打开状态(即不defer关闭,或改用全局句柄管理)。
✅ 正确做法二:使用 systemd(Linux 推荐方案)
相比 daemonize,systemd 更可靠、可监控、支持自动重启与日志整合(journalctl):
- 创建服务文件
/etc/systemd/system/tracking-api.service:[Unit] Description=Tracking API Service After=network.target
[Service] Type=simple User=www-data WorkingDirectory=/var/www/repo/bin ExecStart=/var/www/repo/bin/tracking_api Restart=always RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=tracking-api
[Install] WantedBy=multi-user.target
2. 启用并启动: ```bash sudo systemctl daemon-reload sudo systemctl enable tracking-api.service sudo systemctl start tracking-api.service
- 查看日志:
sudo journalctl -u tracking-api -f
✅ 优势:无需修改 Go 代码、日志自动轮转、崩溃自恢复、权限隔离清晰。
❌ 避免错误尝试
-
daemonize /path/to/binary 1>>log 2>&1无效:daemonize已关闭 stdio,重定向失效; - 仅重定向 stdout(
1>>)而不处理 stderr:Go 日志仍不可见; - 使用
nohup+&:缺乏进程管理能力,易成孤儿进程,不推荐生产环境。
总结
对于 Go 服务部署,优先采用 systemd 管理进程,兼顾健壮性与可观测性;若必须使用 daemonize,务必在 Go 代码中调用 log.SetOutput() 显式指定日志文件句柄。同时建议升级日志方案:引入 log/slog(Go 1.21+)或结构化日志库(如 zerolog、zap),以支持 JSON 输出、字段增强与上下文追踪。










