拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux日志轮转实战:logrotate配置详解与排坑指南

Linux日志轮转实战:logrotate配置详解与排坑指南 上周一个朋友半夜找我说服务器磁盘满了业务告警响个不停。我登上去第一眼就看到罪魁祸首/var/log/nginx/access.log47G。df -h 看一眼根分区剩不到 10%。这种情况我处理过太多次了工具其实一直在系统里就是那个叫 logrotate 的命令。很多人把它当Linux 命令大全里的一个条目背过知道它是管日志轮转的但真正要配起来一堆参数往配置文件里一放什么时候触发、日志切了为什么服务还在写旧文件、copytruncate 和 create 到底有什么不同能讲清楚的人真不多。这篇是 Linux 系统管理实操的一部分今天单独把 logrotate 拎出来说透。你会看到它怎么被 cron 唤醒、怎么读 /etc/logrotate.conf 和 /etc/logrotate.d 下的规则、size 和 daily 有什么区别、copytruncate 和 create 该怎么选最后我会用一个完整实验展示从配置到验证的整个过程再把我这些年踩过的坑一条条列出来。新手可以照着做老手可以跳着看核心目标只有一个别让日志在你手里把磁盘写爆。1. 先搞懂 logrotate 的工作机制后面踩的坑会少一半1.1 日志为什么必须轮转日志文件如果不加控制它就是一个只增不减的巨型文件。业务量小的服务器可能几天看不出来流量稍微上来一点access.log 一天长几个 G 很正常。磁盘被写满之后不只是日志写不进去业务程序可能连普通文件都创建不了数据库、缓存、临时目录全部受影响整台机器就跟宕机差不多。我见过有人想省事直接用 /var/log/nginx/access.log清空文件。看起来简单但有两个问题一是文件句柄没变nginx 写入的偏移位置还是原来的清空之后继续写会在文件头部留下空洞二是历史日志没了事后排查问题完全没有线索。更合理的方式是做轮转也叫 rotation把当前日志文件改名或复制留下一个全新的空日志给服务继续写旧日志按策略压缩、保留、清理。打个比方就是家里的垃圾桶满了要倒掉但不能把垃圾桶也扔了。logrotate 就是那个帮你倒垃圾、还顺手记下上周垃圾处理记录的工具。1.2 logrotate 不是守护进程是 cron 叫醒的logrotate 本身不是一个常驻内存的守护进程它平时什么都不干。真正叫醒它的是 cron由系统每天执行一次。不同发行版放的地方略有差别最常见的是 /etc/cron.daily/logrotate也有用 systemd timer 的比如 logrotate.timer。但不管哪种方式本质都是周期性调用一次/usr/sbin/logrotate /etc/logrotate.conf主配置 /etc/logrotate.conf 里一般会有一行include /etc/logrotate.d意思是把 /etc/logrotate.d 目录下所有规则文件都加载进来。这也解释了为什么我们很少直接改主配置文件而是在 /etc/logrotate.d/ 下新建一个规则文件。logrotate 每次执行时会读一个状态文件通常位于 /var/lib/logrotate/statusCentOS 系可能是 /var/lib/logrotate/logrotate.status里面记录了每个日志文件上次处理的时间和当时的大小。判断是否需要轮转就是拿当前时间、当前文件大小跟状态文件里的记录比一比满足条件就执行不满足就跳过。理解这一点很重要。很多新手排查为什么我的日志没切时重点全放在配置文件上却忘了看一下 cron 到底有没有跑、状态文件里记录了什么。后面第 4 部分我会详细讲排查思路。1.3 为什么推荐 logrotate 而不是自己写脚本有人会觉得不就是一个改名、压缩、删旧文件的流程吗自己写个 shell 脚本放到 crontab 里不就行了原则上可以但我强烈不建议这么干。自研脚本最麻烦的是处理各种边界情况日志文件不存在怎么办文件是空的要不要切进程没重启导致日志写进了旧文件怎么办压缩失败要不要报警多个应用的日志都交给一个脚本管轮转周期还不一样脚本很快就会变成没人敢动的屎山。logrotate 把这些场景都做成了配置项你只需要声明多久切一次、保留几份、要不要压缩、切完执行什么命令剩下的逻辑它帮你兜底。再加上 logrotate 有-d调试模式可以预演一遍轮转过程但不实际执行任何操作。这个能力自研脚本很难做到排障时简直救命。所以老老实实用现成的不要重复造轮子。2. 配置文件语法与核心参数拆解2.1 配置从哪来主配置文件和规则目录先看主配置文件 /etc/logrotate.conf 长什么样不同发行版略有区别但核心结构差不多weekly rotate 4 create dateext include /etc/logrotate.d /var/log/wtmp { monthly create 0664 root utmp rotate 1 }主文件里可以写全局默认参数比如weekly代表默认每周轮转rotate 4代表默认保留 4 份dateext代表默认文件后缀用日期。这些默认值会被 /etc/logrotate.d/ 下的规则文件里同名参数覆盖。规则文件的命名一般和对应的服务同名比如 nginx、apache2、syslog、docker 等。内容就是一个日志文件路径加上花括号里的参数块。路径支持通配符比如/var/log/nginx/*.log能把 access.log、error.log 以及各种虚拟主机的日志一网打尽。写新规则很简单在 /etc/logrotate.d/ 下新建一个文件把规则填进去然后跑一遍语法检查就行。但是要注意每个规则文件的每一行配置都必须合法如果有一个参数名写错了logrotate 会在解析阶段就报错整个文件会被跳过规则不会生效。2.2 必会参数频率、轮转数、压缩、后缀下面是我每次配置都会核对一遍的核心指令按使用频率排序。参数作用使用提示daily / weekly / monthly按时间周期轮转实际触发取决于 cron 的执行时间不是精确的 0 点size 100M日志超过指定大小就轮转后缀支持 K、M、G适合流量波动大的场景maxsize 100M时间周期到或大小超限就轮转需要 logrotate 3.8.5比 size 更灵活rotate 3保留 3 份轮转日志当前正在写的日志不算在内所以最多看到 4 个文件compressgzip 压缩轮转后的日志生成 .gz 文件省磁盘但占 CPUdelaycompress延迟到下次轮转再压缩配合某些还握着旧文件句柄的服务使用dateext轮转文件名用日期后缀避免同名覆盖配合 dateformatdateformat -%Y%m%d自定义日期格式必须以横杠开头支持 %s 秒级时间戳missingok日志文件不存在时跳过服务还没启动时不至于报错notifempty空文件不轮转建议永远加上否则会生成一堆空压缩包create 0640 nginx adm轮转后创建新的空日志文件注意属主和权限要能让应用继续写copytruncate复制内容到新文件后清空原文件对进程更友好但复制和截断间隙可能丢少量日志sharedscripts多个匹配文件时 postrotate 只执行一次配 nginx 这种多文件服务时必用postrotate / endscript轮转后执行的脚本常用于通知服务重新打开日志文件这里多说一句rotate。很多人以为 rotate 3 是最多有 3 个日志文件其实是轮转后的旧日志保留 3 份加上正在写的那份一共 4 个文件。如果老日志超过 3 份logrotate 会从最旧的开始删除。所以如果你有保留半年的合规要求记得把 rotate 天数设够。2.3 copytruncate 和 create 怎么选这两个参数是 logrotate 最核心的两种工作方式我见过很多配置写错导致日志切了等于没切的情况。先说原理。create 方式是默认做法把当前日志文件改名为轮转文件然后在原路径新建一个空文件。简单粗暴但有个隐患——如果你的服务进程一直握着旧文件的句柄改完名之后它还在往旧文件里写。所以要配上 postrotate给服务发信号让它重新打开日志文件。比如 nginx 发 USR1 信号rsyslog 发 HUP 信号。copytruncate 方式则是不动原文件的 inode先复制当前内容到轮转文件再把原文件截断清空。进程从头到尾没感知到文件被处理过不需要发信号对应用最友好。缺点有两个一是复制过程中可能有少量新写入日志丢失二是文件很大时复制有 IO 开销。怎么选其实看你的服务能不能重新打开日志文件场景推荐方式原因nginx、rsyslog、系统日志等支持信号的服务create postrotate日志不丢失轮转干净Java 应用、很多自研服务没实现信号重开copytruncate进程无感知接入成本最低日志量极大、担心复制开销create postrotate避免复制大文件带来的 IO 压力一个典型翻车现场配置写了 create但没写 postrotate或者 postrotate 里的命令写错了。结果就是轮转后新日志一直不增长所有日志还在那个被改了名的旧文件里。排查了大半天才发现是进程句柄的问题。2.4 一套可以直接抄的配置模板拿 nginx 举个例子你可以在 /etc/logrotate.d/nginx 里写这样一套/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok notifempty create 0640 nginx adm sharedscripts postrotate [ -f /var/run/nginx.pid ] kill -USR1 $(cat /var/run/nginx.pid) endscript }解释几个关键点。daily决定按天轮转rotate 14保留两周compress压缩旧日志。delaycompress配合 nginx 使用是稳妥操作因为 nginx 收到 USR1 信号重新打开日志文件需要一点点时间延迟压缩可以避免把可能还在写的文件意外压缩。create 0640 nginx adm保证新日志文件属主是 nginx权限 0640既能写入又避免日志内容暴露给无关用户。sharedscripts一定要加。如果 /var/log/nginx/ 下有多个日志文件不加这个参数的话postrotate 脚本会对每个文件执行一次nginx 会收到一堆 USR1 信号。加了之后所有匹配文件轮转完成才执行一次脚本干净利落。postrotate里的核心是判断 pid 文件存在后给 nginx 主进程发 USR1 信号。这个信号是 nginx 特别约定的收到之后会优雅地重新打开日志文件不会中断请求。3. 实操演示完整配置一次日志轮转3.1 准备测试日志前面讲理论现在做实验。我建议你在 /tmp 下面先跑一遍不要一上来就改生产规则。这样权限问题少出错了也不影响系统日志。先建目录造一个带内容的日志文件mkdir -p /tmp/logdemo/logs touch /tmp/logdemo/logs/app.log for i in $(seq 1 200000); do echo 2024-01-01 12:00:00 INFO fake log line $i /tmp/logdemo/logs/app.log done du -h /tmp/logdemo/logs/app.log用du看一下这个文件应该有 10M 左右纯粹是为了让 size 条件能触发。如果你造出来的太小可以加大seq的数值。3.2 写轮转规则在 /etc/logrotate.d/ 下新建一个规则文件就叫 logdemovi /etc/logrotate.d/logdemo写入内容/tmp/logdemo/logs/*.log { size 5M rotate 3 compress dateext dateformat -%Y%m%d-%s missingok notifempty copytruncate }这套规则的意思很简单日志超过 5M 就切保留 3 份切成的新文件名带日期和秒级时间戳轮转后压缩文件不存在或为空时跳过用 copytruncate 方式处理。为什么演示用 copytruncate因为测试时我不想处理进程信号的问题也不想为它专门写一个守护进程。copytruncate 对进程透明验证结果最直观。真实环境里怎么选参考前面 2.3 的表格。3.3 先跑 debug 模式检查写完之后不要急着强制轮转先做语法检查和预演logrotate -d /etc/logrotate.d/logdemo-d是 debug 模式只打印会执行的操作不真正动文件也不会更新状态文件。输出里重点看这几行reading config file /etc/logrotate.d/logdemo Reading state from file: /var/lib/logrotate/status Handling 1 logs rotating pattern: /tmp/logdemo/logs/*.log 5242880 bytes (3 rotations) considering log /tmp/logdemo/logs/app.log log needs rotating如果看到 log needs rotating说明规则没问题触发条件也满足了。如果报错日志文件路径、参数名、权限这些基本都能在解析阶段暴露出来提前发现。3.4 强制轮转并验证结果debug 模式没有副作用现在强制执行一次看看真实效果logrotate -vf /etc/logrotate.d/logdemo-v是 verbose会打印处理的详细信息-f是 force强制轮转不管条件满不满足。执行完看目录里的变化ls -lh /tmp/logdemo/logs/你会看到 app.log 被截断成很小目录里多了几个带时间戳的压缩包。用gzip -l看看压缩包内容gzip -l /tmp/logdemo/logs/app.log-*.gzgzip -l能直接列出压缩前的原始大小可以确认日志内容确实被搬到了轮转文件里。如果还想看内容用 zcat 配合 headzcat /tmp/logdemo/logs/app.log-*.gz | head -3再查一下状态文件确认 logrotate 记住了这次处理cat /var/lib/logrotate/status | grep logdemo不同发行版的状态文件格式不太一样有的是纯文本记录有的可能是 JSON但核心都是日志路径和处理状态。看到里面有 logdemo 的记录就说明一次完整的轮转闭环已经跑通了。3.5 让定时任务接管规则已经验证没问题最后确认一下系统有没有自动跑。cat /etc/cron.daily/logrotate大多数 Linux 发行版会在/etc/cron.daily/下放一个 logrotate 脚本内容核心就是执行/usr/sbin/logrotate /etc/logrotate.conf。只要这个文件存在且 cron 服务正常你的新规则就会跟着每天执行一次。如果你用的系统是 systemd timer 驱动的可以检查一下systemctl status logrotate.timer有就放心没有也没关系绝大多数场景 cron.daily 都足够了。到了这一步整个配置从写规则到自动生效就全部完成了。4. 排查实录我踩过的 logrotate 的坑4.1 配置了却不轮转先按顺序查这几件事明明写了规则日志就是不切是我收到最多的求助。从经验看按以下顺序排查一般几分钟就能定位。现象大概率原因验证 / 解决方式日志老不切状态文件记录还没到期logrotate -d看是否需要轮转加-v看细节规则没生效路径写错或通配符没匹配检查日志文件的真实路径ls确认日志不存在服务没启动或文件被删加missingok容忍缺失完全没动静cron 没跑或者定时任务被改了cat /etc/cron.daily/logrotate手动执行一遍前一次报错规则文件里有未知参数用logrotate -d解析错误会直接打出来特别提醒调试时先-d后-f。-d是预演-f是强制。不要在没搞清楚条件判断逻辑之前就-f因为 force 会无视 notifempty、无视时间周期把所有匹配的日志全部切一遍。生产环境这么搞很可能把一堆不需要处理的日志文件切出很多空压缩包。4.2 postrotate 脚本里 reload 失败日志写进旧文件这是 create 方式下最典型的坑。现象是轮转确实执行了新日志文件也创建了但文件大小一直为 0所有日志还在那个被改名成 app.log-20240101 的旧文件里涨。原因几乎都是 postrotate 脚本没有真正生效。常见情况有三种脚本里路径写错pid 文件路径不对服务不是用信号重开日志的机制。排查方法很简单手动执行一遍 postrotate 里的命令看看服务是否重新打开了日志文件cat /var/run/nginx.pid kill -USR1 $(cat /var/run/nginx.pid)然后观察新日志文件是否开始增长。如果手动可以、自动不行多半是sharedscripts没加或者 postrotate 脚本里的条件判断有问题。如果手动也不行说明你这个服务根本不支持信号重开日志老老实实换 copytruncate 吧。4.3 轮转出来的文件权限不对应用写不进去另一个高频问题是轮转后应用突然写不了日志了报 permission denied。原因通常出在create参数上。比如你写create 0644 root root轮转后新文件属于 root应用却以普通用户身份运行自然写不进去。正确做法是让新文件的属主匹配应用用户或者至少让应用用户在这个文件上有写权限。一个合理的 nginx 写法是create 0640 nginx adm这样文件属主是 nginx组是 adm权限 0640。nginx worker 进程能写其他用户读不了。日志常常包含请求参数、用户名等半敏感信息0640 比 0644 更合适这是很多配置模板里默认带create 0640的原因。另外记得如果日志目录不是默认的 /var/log/xxx而是你自定义的路径还要确认父目录权限。logrotate 创建新日志文件时需要对目录有写权限/tmp 这类目录没问题但某些服务目录权限收得很紧容易翻车。4.4 debug、force、status三个最容易被用错的参数-d和-f我前面强调过了这里再展开一点。-d是 debug不会改文件也不会改状态文件它最大的作用是让你知道logrotate 认为这个日志需不需要切、打算怎么切。有些版本在 debug 模式下看到 log does not need rotating不代表配置有问题只是条件没满足。-f是 force会无视所有条件强制执行。但 force 也有副作用对同一个配置同一天多次 force配合 dateext 时可能因为目标文件已存在而报错。避免方法是用更细的日期格式比如dateformat -%Y%m%d-%s秒级时间戳在正常操作下很难撞名称。-s/--state可以指定状态文件路径在测试环境特别有用。比如我想调试一套规则又不想动系统真正的状态文件就可以用logrotate -d -s /tmp/logrotate.state /etc/logrotate.d/logdemo但生产环境不要随意改状态文件路径否则 logrotate 找不到历史状态可能会把一批本来不该切的日志全切了或者相反一直认为上次刚处理过而不动作。4.5 容易被忽略的三个小细节第一个是notifempty。不加这个参数轮转时空日志文件也会被切。日志文件如果是空的切出来一个几字节的压缩包纯粹是浪费。更重要的是如果服务在轮转瞬间刚好没有输出空文件被切走新文件又被创建日志内容在时间线上会出现断层。加上notifempty一劳永逸。第二个是rotate 0。这个写法表示不保留任何旧日志轮转后压缩包会被立即删除。有些场景确实需要比如临时测试盘的日志但如果你有审计或者排障需求千万别设 0。第三个是注意应用自带的日志切片逻辑。很多编程语言的日志库自带 RotatingFileHandler 之类的切割机制比如 Python 的 logging、Java 的 log4j。如果应用自己每天切日志logrotate 再按 size 去切可能把已经切走的文件又处理一遍。正确的做法是二选一应用自己切logrotate 只负责清理压缩或者 logrotate 切应用侧关闭自带切割功能。两头都管日志就会乱成一锅粥。我平时在实际操作中的体会是logrotate 配置本身并不难难的是搞清楚每个服务在日志被移动之后会怎么反应。拿到一个陌生服务先看它能不能响应信号、会不会重新打开日志文件再决定 create 还是 copytruncate。顺手把 debug 模式跑一遍确认没有任何解析错误最后再放到生产环境。按照这个流程走你基本不会重蹈那些日志切完服务挂了的覆辙。
返回列表