go 中调用 rsync 同步日志前必须确认三件事:远程目标目录对 ssh 用户可写且支持删除、本地日志路径为绝对路径、ssh 私钥免密且无密码;参数需精简,必加 --archive --delete --exclude,禁用 --progress 和 -v;退出码解析比 err 判断更重要,须捕获 stderr 并记录具体 code 与错误;同步任务应交由系统 cron 管理,go 仅监听文件 rename 事件触发单文件补传。

Go 里调用 rsync 同步日志前必须确认的三件事
直接 exec.Command("rsync", ...) 很可能在生产环境静默失败。关键不是“能不能跑”,而是“失败时你能不能定位到是权限、网络、路径还是 rsync 本身没装”。
- 远程目标目录(如
/var/log/myapp/)必须对 SSH 用户可写,且rsync --delete要求该用户对目录内文件有删除权 - 本地日志路径必须用绝对路径 ——
./logs/在 cron 或 systemd 下会因cmd.Dir未设而指向根目录或 /tmp,导致源路径不存在 - SSH 密钥必须免密登录,且私钥不能带密码;否则
rsync在后台运行时卡住,cmd.Run()永远不返回
rsync 命令参数要精简,别堆 -vz 就完事
日志同步不是传大文件,重点在可靠性和低干扰。加太多参数反而放大风险。
- 必加:
--archive(等价于-rlptgoD)保权限、时间戳、软链;--delete清理过期日志;--exclude="*.tmp"避开正在写入的临时文件 - 慎用:
--compress—— 日志文本压缩率有限,但压缩过程可能把敏感字段(如 trace_id、token 片段)留在内存页中,被 dump 出来 - 禁用:
--progress和-v—— 它们往 stdout 写大量行,Go 捕获后易撑爆 buffer;真要调试,改用--log-file=/tmp/rsync.log单独落盘
Go 中解析 rsync 退出码比判断 err != nil 更重要
err != nil 只告诉你“出错了”,但日志同步场景下,exit code 23(部分失败)和 exit code 12(找不到命令)处理方式天差地别。
- 必须显式捕获 stderr:
var stderr bytes.Buffer; cmd.Stderr = &stderr - 检查是否为 ExitError:
if exitErr, ok := err.(*exec.ExitError); ok,再取exitErr.ExitCode() - 常见需告警的退出码:
23(权限不足或磁盘满)、24(文件被删)、12(rsync 找不到)、5(无访问权限) - 日志里至少记两行:
rsync exited with code %d: %s和stderr: %s,否则下次排查只能靠猜
别用 time.Ticker 做定时同步,cron 是唯一靠谱选择
微服务里起个 goroutine + time.Ticker 看似简单,实则埋雷:进程重启后定时器丢失、多实例并发触发、无法统一启停、资源泄漏难监控。
- 所有 rsync 同步任务必须交给系统 cron 管理,例如:
0 * * * * cd /opt/myapp && /usr/bin/rsync -a --delete --exclude='*.tmp' /var/log/myapp/ user@logserver:/data/logs/myapp/ >> /var/log/rsync.log 2>&1 - Go 进程只做一件事:监听本地日志目录变更(用
fsnotify),发现新生成的app-2026-06-30.log后,触发一次rsync --ignore-existing单文件同步,补上实时缺口 - 注意
fsnotify监听的是 rename 事件而非 create —— 日志库(如 zap)先写app-2026-06-30.log.tmp,再原子 rename,监听 create 会拿到空文件
真正麻烦的从来不是“怎么让 rsync 跑起来”,而是当某台节点的 /var/log/myapp/ 权限突然变成 root:root 750、或 SSH 私钥被轮换、或 rsyncd 服务端配置漏了 read only = no 时,你有没有在日志里留下足够线索去秒级定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











