软链接失效源于logrotate重命名或删除原文件后未更新链接,修复需用create+copytruncate确保inode不变,或在postrotate中动态重建链接。

日志切割后原文件的软链接失效,本质是 logrotate 把旧文件重命名或删了,而软链接还死死指着那个已不存在的路径——它不会自动更新。修复不是“修链接”,而是让链接行为跟上日志轮转节奏。
为什么 logrotate 后 access.log 软链接变红、readlink -f 报错
典型场景:/var/log/nginx/access.log 是个软链接,指向 /var/log/nginx/access.log.1;logrotate 执行后把 .1 改成 .2,再新建 .1,但软链接仍指老 .1(已不存在)或被直接覆盖丢失。
关键点:
-
logrotate默认不碰软链接本身,只处理它指向的“真实文件” - 如果软链接目标是带数字后缀的滚动文件(如
access.log.1),每次轮转后该路径必然失效 - 用
ls -l看到软链接名是红色、或readlink /var/log/nginx/access.log返回空,就确认断了
在 logrotate 配置里用 create + copytruncate 避免手动干预
根本解法:别让软链接指向滚动编号文件,改让它始终指向“当前正在写的那个文件”。靠 logrotate 的两个参数配合:
-
create 644 www-data www-data:轮转后自动创建新日志文件,权限和属主按指定设好 -
copytruncate:先拷贝再清空原文件(而不是 rename),保证软链接始终有效——因为原文件 inode 没变,只是内容被截断
示例配置片段(/etc/logrotate.d/nginx):
"/var/log/nginx/*.log" {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 www-data www-data
sharedscripts
copytruncate
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
注意:copytruncate 对高写入量日志有极小丢日志风险(拷贝和截断之间有毫秒级窗口),但比软链接断裂导致服务异常要可控得多。
用 postrotate 脚本动态重建软链接
如果必须保留编号路径语义(比如监控脚本硬编码读 access.log.1),那就让 logrotate 在每次轮转后主动刷新链接:
- 在
postrotate里加一行:ln -sf /var/log/nginx/access.log.1 /var/log/nginx/access.log - 确保
access.log.1真的存在(即rotate值 ≥ 1,且没被压缩) - 如果用了
compress,得改成delaycompress,否则.1会被压成.1.gz,链接又断
更健壮的写法(防目标不存在):
postrotate
if [ -f /var/log/nginx/access.log.1 ]; then
ln -sf /var/log/nginx/access.log.1 /var/log/nginx/access.log
fi
endscript
容器环境里别依赖宿主机的 logrotate
容器内日志路径常挂载自宿主机,但 logrotate 运行在宿主机,而容器进程看到的仍是挂载后的路径。容易出现:宿主机轮转了,容器里 readlink -f /app/logs/app.log 却解析出宿主机路径,权限或命名不一致。
推荐做法:
- 容器内禁用外部 logrotate,改用
docker logs+json-file日志驱动,由 Docker 统一轮转 - 若必须用文件日志,让容器自己跑轻量轮转(如
logrotatein container 或newsyslog),避免跨环境路径解析歧义 - 软链接目标一律用绝对路径,且确保在镜像构建时就存在(
RUN ln -sf /var/log/app/current.log /var/log/app/latest.log)
最易被忽略的一点:软链接失效本身不报错,但后续所有基于它的操作(tail -F、grep、日志采集 agent)会静默失败或卡住——得靠监控检查 test -e /path/to/link && test -r /path/to/link 是否为真。

















