type: learning-note
title: Linux:日志轮转 logrotate------日志怎么不撑爆磁盘
date: 2026-10-12
description: 为什么需要轮转(撑爆 vs 丢证据)、轮转是一组五步动作、轮转不是备份、配置在哪(主文件+片段目录+include)、策略结构、missingok/notifempty、weekly 与 rotate、compress/delaycompress、create、fd 与 postrotate、copytruncate、-d 预演与 -f 强制
2026-10-12 学习笔记
> 📌 GitHub:https://github.com/AR-firefly
笔记区
核心观点
**为什么必须有轮转:两个方向都要防**
日志是**一直在写**的,不会自己停。只有两条路,两头都不能走极端:
-
**不管它** → 某天撑爆文件系统。盘满不只是"日志写不进去"------服务起不来、程序崩,整台机器跟着出事
-
**嫌它大直接删** → 删掉的是**证据**。等出事要查"那天谁登进来的""服务为什么重启",现场已经没了
所以需要一个东西,在这两头之间**定一套规矩**:什么时候转、留多少代、压不压缩、超了删哪个。
**两个词**:
-
**轮转(rotation)**:把正在写的日志**换成一个新文件接着写**,老的改名留下来
-
**`logrotate`**:Linux 上专门干这件事的程序,由定时任务定期叫它起来
「轮转是为了防止日志爆满和把日志全删了这两件事,所以必须存在轮转」
**轮转不是一个命令,是一组动作**
LJ 原话(`managing-log-files`):A typical rotation renames the active file, creates a replacement, optionally asks the application to reopen it, compresses older generations, and removes files beyond retention.
落到 `auth.log` 上就是五步:
-
**改名**:正在写的 `auth.log` → `auth.log.1`
-
**建顶替的**:新开一个空的 `auth.log`,程序照旧往这个名字写
-
**(可选)叫应用重开**:程序手里还攥着旧文件时,通知它一声
-
**压缩**:更老的那些变成 `.gz`
-
**删超保留**:超出"留几代"的最老的,删掉
⚠️ 3、4、5 做不做、怎么做,**全看配置**------原文里那两个词 optionally 和 beyond retention 就是这意思。
**`.1` 是轮转自己造出来的**------它是这个序列的第一步:把正在写的那个改名成 `.1`。
```
转之前: auth.log ← 程序在写
转之后: auth.log ← 新的空本
auth.log.1 ← 刚才那本,改名过来的
```
再转一次就往下排:`auth.log`(在写)+ `.1`(新退下来的)+ `.2.gz`(上一代,已压)+ `.3.gz` + `.4.gz`。**数字越大越老。**
「轮转的整个流程是先改名正在写的,之后建个新的顶替,然后应用重开(可选),压缩,最后才删超过留几代的最老的部分」
**轮转不是备份**
轮转后的那些 `.1` / `.2.gz`,跟正在写的 `auth.log` 待在**同一个目录、同一块盘**上。所以:
-
这块盘**物理坏了** → 五代一起没
-
**误删**、被人一锅端 → 一起没
-
**保留策略到点** → 最老的那代照样删掉
LJ 原话:...rotation is not a backup because retained copies can still be deleted, corrupted, or lost with the same host.
**备份的意思是另存一份到别处**,而且不归这套保留策略管。轮转干的是"把屋子里的东西码整齐",不是"在别处再盖一间房"。
用户的原话:「轮转不是备份,丢东西的时候是一起没的」
**配置在哪:主文件 + 片段目录**
LJ 原话:The main file is commonly /etc/logrotate.conf, with package or application snippets under /etc/logrotate.d/.
-
**主文件**:`/etc/logrotate.conf`(总规章)
-
**片段目录**:`/etc/logrotate.d/`(各家软件把自己那份规则丢这儿)
主文件里靠 **`include /etc/logrotate.d`** 把整个抽屉读进来。`man` 原文:`include file_or_directory` ------ Reads the file given as an argument as if it was included inline where the include directive appears.(给的是目录时,里面的文件按**字母顺序**读,非普通文件和 taboo 后缀的会跳过。)
所以**新丢一个文件进 `logrotate.d/`,不用回主文件登记,它自动生效**。
这套"主文件 + `.d` 片段目录"的套路,和第 3 天学的 rsyslog(`/etc/rsyslog.d/`)是**同一个思路**------各管各的放抽屉里,别让一个文件越写越长。
**一条策略的结构**
```
路径 {
指令
指令
}
```
骨架就两样:**一行路径** + **一对花括号**。
⚠️ **一条策略可以同时挂好几条路径、共用同一组指令**------本机 `/etc/logrotate.d/rsyslog` 就是 6 条路径(`syslog` / `mail.log` / `kern.log` / `auth.log` / `user.log` / `cron.log`)共用一组。
两个容错指令(都写在括号里):
-
**`missingok`**:路径写的那个文件**不存在**时,**不报错**、跳过去接着干(`man` 原文:If the log file is missing, go on to the next one without issuing an error message.)
-
**`notifempty`**:文件**是空的**就别转(`man`:Do not rotate the log if it is empty)
反过来:`nomissingok` 才是**默认**------不写这条,文件不在会报错。
**`weekly` 和 `rotate 4` 不是一回事**
-
**`weekly`** = 隔多久**判一次**该不该转(管频率)
-
**`rotate 4`** = 最多**留几代**(管库存)
`man` 原文:`rotate count` ------ Log files are rotated count times before being removed...
⚠️ **`rotate N` 数的是"老的"几代,活跃那个不在名额里**------总文件数 = **1 + N**。
本机实物对得上:`auth.log`(活跃)+ `.1` + `.2.gz` + `.3.gz` + `.4.gz` = 活跃 1 个 + 老的 4 代,正是 `rotate 4`;时间依次 10-12 / 10-10 / 10-08 / 09-29 / 09-19,近一周一份,正是 `weekly`。
用户的原话:「他是指保存多少个老的,也就是说总共是新的一个+rotate里的n个」
**`compress` 和 `delaycompress`**
-
**`compress`**:老的几代会被**压缩**(默认抓 gzip,所以后缀是 `.gz`)
-
**`delaycompress`**:让**刚退下来的那一代先不压**,等**下一次轮转**再压
`man` 原文:`delaycompress` ------ Postpone compression of the previous log file to the next rotation cycle. ...It can be used when some program cannot be told to close its logfile and thus might continue writing to the previous log file for some time.
为什么要拖一下:万一写日志的程序**还没松手**,还能往那一代写;压了就写不进去了。
**`.gz` 这个名字的来历**:gzip 压出来的文件习惯加 `.gz`。`man` 原文:`Old versions of log files are compressed with gzip(1) by default.`
**`create` ------ 轮转后顶上来的新文件归谁**
写法 `create <权限> <属主> <属组>`:
-
`/etc/logrotate.d/dpkg` 写的是 `create 644 root root`
-
`/etc/logrotate.d/btmp` 写的是 `create 0660 root utmp`
-
`/etc/logrotate.d/wtmp` 写的是 `create 0664 root utmp`
⚠️ **不写 `create` 会怎样**:`man` 原文:Any of the log file attributes may be omitted, in which case those attributes for the new file will use the same values as the original log file for the omitted attributes. ------ **省略的那几项,新文件沿用原文件的同项值**。
所以不是"全按配置来",是"**写了按写的、没写抄旧的**"。
**`minsize`**:Log files are rotated when they grow bigger than size bytes, **but not before** the additionally specified time interval ------ **时间到点了、而且够大了,两个都满足才转**。本机 `/etc/logrotate.d/wtmp` 写的是 `minsize 1M` + `monthly`。
**fd 与 `postrotate`:改名之后,还在写的那个程序怎么办**
进程打开一个文件时,内核给它一张"**凭条**",凭这个读写。这张凭条叫 **文件描述符(file descriptor,fd)**。
**关键:fd 指向的是文件本身(inode),不是文件名。**
这条接上了 09-21 学的"改名不动 inode"。所以:
-
轮转把 `auth.log` → `auth.log.1`:**名字变了,文件本身没变**
-
程序手里那张凭条还指着同一个文件
-
**结果:它继续往 `auth.log.1` 写**,而新建的 `auth.log` 一直是空的
LJ 原话:After renaming a log, a daemon can continue writing through its still-open file descriptor.
**`postrotate` 就是喊它一声的地方**------在里面写一段脚本,通知程序**重新打开日志文件**:
```
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
```
-
**`postrotate` / `endscript` 是一对**,中间夹一段 shell 脚本,两个关键字各自单独占一行(`man` 的 `SCRIPTS` 节)
-
**时机**:轮转做完之后、压缩之前(`man`:executed after the log file is rotated and before the log file is being compressed)
-
本机那个脚本干的事:给 rsyslog 发一个 **HUP 信号**,让它重开日志
**时间线**(这是理解的关键):
-
轮转改名 → **从这刻起,程序还在往老那本(`.1`)写**
-
建新空文件 `auth.log`
-
跑 `postrotate` → 发信号
-
**程序收到 → 松手、重新打开 → 从这一刻起,新日志进新的 `auth.log`**
「`postrotate` 里的东西的作用就是提醒他写新文件了的东西」
**rsyslog 只有一个进程**
系统里就**一个** rsyslog,它**同时**写着好几份日志(那份策略里的 6 条路径)。新建文件时**不会多出一个进程**------还是那一个,只是它手里要多拿一张凭条。
**"喊一声"不是重启整个程序**,是让它把手里那几张凭条重新拿一遍。
**`copytruncate` ------ 程序喊不动时怎么办**
点 9 那套的前提是"程序能被喊动"。有些程序根本没写重开逻辑,喊它也不理。`copytruncate` 就是为这种程序准备的,它**不走改名那条路**:
-
**不改名**
-
**复制**一份当前日志出来(留作上一代)
-
把**原文件就地清空成 0**(名字没变、位置没变,程序手里那张凭条还对着它------可里面空了)
程序毫无察觉,继续写------**所以压根不用喊它**。
`man` 原文:`copytruncate` ------ Truncate the original log file to zero size **in place** after creating a copy, instead of moving the old log file... It can be used when some program **cannot be told to close its logfile**... Note that there is a **very small time slice between copying the file and truncating it, so some logging data might be lost**.
⚠️ **代价**:复制和截断之间那一小段窗口里,新写进来的日志**会丢**。所以它是**折中,不是默认**。
⚠️ 它**不是剪切**:`mv` 是搬走原件;`copytruncate` 是"复制 + 清空",**原件一直留在原地**。
**错配的后果**:喊不动的程序 + `postrotate` 那套 → 日志**全堆进 `.1`**,而 `.1` 在轮转眼里是"上一代",下一轮被挤到 `.2`、再往后**直接被删** ------ 程序正把日志往一个注定被删的文件里写。
**`-d` 预演 vs `-f` 强制执行**
-
**`-d`(debug)**:**只打印它"打算"怎么做**------不动任何日志文件,**也不更新状态文件**
-
**`-f`(force)**:**强行真转**------哪怕它判断"今天不该转",也照转不误
`man` 原文:`-d, --debug` ------ Turn on debug mode, which means that **no changes are made to the logs and the logrotate state file is not updated**. Only debug messages are printed.
⚠️ LJ 提醒:**`-d` 输出好看,不等于真跑一定成功**(权限、脚本、磁盘空间、程序能不能重开------这些它验不了)。
⚠️ 状态文件 `/var/lib/logrotate/status` 记的是"上次什么时候转的"。**普通用户读不到它时,`-d` 会假设一个"空状态"**,据此给出的判断**未必等于真跑结果**。
**(了解层)轮转管不到的地方**
-
`logrotate` **只管它策略里点名的文件**------没写进配置的路径,一概不管
-
**systemd journal 有它自己的一套**:本机那 720M 的 journal **不归 logrotate 管**,归 `/etc/systemd/journald.conf`(比如 `SystemMaxUse=`)
-
数据库、远端日志服务器,各管各的
关键方法/流程
-
**看它的规则**:`ls -la /etc/logrotate.d/` → `cat /etc/logrotate.conf`(找 `include` 那行)
-
**读一份策略**:`cat /etc/logrotate.d/rsyslog`(数路径行)/`cat /etc/logrotate.d/wtmp`(逐行读指令)
-
**看轮转的结果**:`ls -l /var/log/auth.log*`(活跃 + 各代,时间验频率、个数验 `rotate`)
-
**预演(不动任何文件)**:`logrotate -d /etc/logrotate.d/dpkg`
-
**看谁在写**:`pgrep -a rsyslog`(确认只有一个 rsyslog)
-
**读压缩档**:`zgrep 关键词 文件.gz` / `zcat` / `zless`(不解压也能读;`gunzip` 会消掉 `.gz`,别在 `/var/log/` 里随手用)
实战记录
**① 五代实物**
```
$ ls -l /var/log/auth.log*
-rw-r----- 1 syslog adm 4800 Oct 12 17:17 /var/log/auth.log
-rw-r----- 1 syslog adm 9236 Oct 10 23:17 /var/log/auth.log.1
-rw-r----- 1 syslog adm 2776 Oct 8 17:17 /var/log/auth.log.2.gz
-rw-r----- 1 syslog adm 4459 Sep 29 13:17 /var/log/auth.log.3.gz
-rw-r----- 1 syslog adm 4562 Sep 19 23:17 /var/log/auth.log.4.gz
```
------活跃 1 个 + 老 4 代 = `rotate 4`;一周一份 = `weekly`;`.1` 纯文本、`.2` 起压缩 = `delaycompress`。
**② `-d` 预演**
```
$ logrotate -d /etc/logrotate.d/dpkg
warning: logrotate in debug mode does nothing except printing debug messages! ...
reading config file /etc/logrotate.d/dpkg
Reading state from file: /var/lib/logrotate/status
error: error opening state file /var/lib/logrotate/status; assuming empty state: Permission denied
...
rotating pattern: /var/log/dpkg.log monthly empty log files are not rotated, (12 rotations), old logs are removed
considering log /var/log/dpkg.log
Now: 2026-10-12 19:26
Last rotated at 2026-10-12 19:00
log does not need rotating (log has already been rotated)
```
------结论"不转";那行 `Permission denied ... assuming empty state` 说明**非 root 读不到状态文件、它自己假设了空状态**,所以这个判断**未必等于真跑结果**。
**③ rsyslog 只有一个进程**
```
$ pgrep -a rsyslog
208 /usr/sbin/rsyslogd -n -iNONE
```
线索区(学完后合上材料,自问自答)
**Q: 为什么必须有轮转?它防的是哪两件事?**
A: 防两个极端------**放开了长会撑爆文件系统**;**砍太狠会丢掉运维/合规要用的证据**。
**Q: 轮转具体做了哪五步?**
A: ① 给活跃文件**改名** ② **建替代文件** 顶上 ③ (可选)**叫应用重开** ④ **压缩**更老的几代 ⑤ **删**超出保留的。3/4/5 做不做看配置。
**Q: 轮转能不能当备份?**
A: **不能**。转出来的副本跟原件在同一台主机、同一块盘,会**一起坏、一起被删**。备份要求另存于别处。
**Q: 规则放在哪两个地方?靠什么串起来?**
A: 主文件 `/etc/logrotate.conf` + 片段目录 `/etc/logrotate.d/`;主文件里靠 **`include /etc/logrotate.d`** 把整个目录读进来。
**Q: `weekly` 和 `rotate 4` 各管什么?**
A: `weekly` 管"**隔多久判一次**"(频率);`rotate 4` 管"**老的最多留几代**"(库存)。总文件数 = **1 + N**。
**Q: 为什么 `.1` 是纯文本、`.2.gz` 才是压缩的?**
A: **`delaycompress`**------刚退下来的那一代先不压(万一程序还没松手,还能写),等下一轮再压。
**Q: 不写 `create` 时,新建的日志文件权限从哪来?**
A: **沿用原文件的属性**(`man`:省略的属性用原文件的同项值)。写了 `create` 就以写的为准。
**Q: 轮转把文件改名之后,还在写的程序会往哪写?为什么?**
A: 往**改名后的那本**(`.1`)写------它手里的 **fd 指着文件本身、不认名字**,改名不动 inode,所以它察觉不到。
**Q: `postrotate` 解决的是什么问题?**
A: 程序不认名字、不会自己换文件;`postrotate` 里的脚本**喊它一声**(本机是发 HUP),让它**重新打开**日志文件,这样新日志才进新的 `auth.log`。
**Q: 新日志从哪一刻开始进新文件?**
A: **`postrotate` 跑完、程序重开之后**。在那之前那一小段窗口,日志还落在老的 `.1` 里。
**Q: `copytruncate` 是干什么的?代价是什么?**
A: 给"**喊不动**的程序"用------复制一份 + 把原件**原地清空**,不用通知程序。代价:**复制和截断之间那一小段窗口,可能丢日志**。
**Q: `-d` 和 `-f` 的区别?哪个安全?**
A: `-d` = **预演**,不动文件、不更新状态文件(安全);`-f` = **强制执行**,哪怕判断不该转也照转。
**Q: `-d` 输出说"不需要转",就一定对吗?**
A: **不一定**。它验不了权限、脚本、空间、程序重开;而且**读不到状态文件时会假设空状态**,判断可能与真跑不同。
总结(50字以内,用自己的话概括今天学到了什么)
日志会一直长,轮转用"改名→建新→喊程序重开→压缩→删老"这套规矩防它撑爆、又不丢证据;配置在主文件加片段目录,改前用 `-d` 预演。
复习卡片
| 概念 | 必学/了解 | 一句话 | 场景 |
| ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| 为什么要轮转 | 必学 | 防**撑爆**(放开长)与**丢证据**(砍太狠)两个极端 | 理解它存在的理由 |
| 轮转(rotation) | 必学 | 把正在写的日志换成新文件接着写,老的改名留下 | 说清它干什么 |
| `logrotate` | 必学 | 干这件事的程序,由定时任务定期叫起来 | 找它的名字 |
| 轮转五步 | 必学 | 改名 → 建替代 → (可选)叫重开 → 压缩 → 删超保留 | 说全流程 |
| **轮转不是备份** | 必学 | 副本跟原件同机同盘,会一起没 | 别把它当保险 |
| `/etc/logrotate.conf` | 必学 | 主文件(总规章) | 找规则 |
| `/etc/logrotate.d/` | 必学 | 片段目录,各家软件把自己那份丢这儿 | 新加规则放哪 |
| `include` | 必学 | 把那个文件/目录的内容读进来,当成就写在这儿 | 主文件怎么吃片段 |
| 策略结构 | 必学 | `路径 { 指令 }`;一条策略可挂多条路径共用一组指令 | 读配置 |
| `missingok` | 必学 | 文件不在**不报错**、跳过去 | 容错 |
| `notifempty` | 必学 | 文件**是空的**就别转 | 容错 |
| `weekly` | 必学 | 隔多久**判一次**(管频率) | 与 `rotate` 分开记 |
| `rotate N` | 必学 | 老的**留 N 代**(管库存);总文件 = 1 + N | 数代数 |
| `compress` | 必学 | 老的几代压缩(默认 gzip → `.gz`) | 压不压 |
| `delaycompress` | 必学 | 刚退下来的那代**先不压**,下一轮再压 | 为什么 `.1` 不压 |
| `create <mode> <owner> <group>` | 必学 | 转完新建文件的权限/属主/属组;**不写则沿用原文件** | 新文件归谁 |
| `minsize` | 必学 | **够大 + 到点**两个都满足才转 | 小文件不转 |
| **fd(文件描述符)** | 必学 | 进程读写文件靠它;它指着**文件本身,不认名字** | 理解 postrotate |
| `postrotate` / `endscript` | 必学 | 一对关键字,中间夹脚本;**轮转后、压缩前**跑 | 喊程序重开 |
| HUP 信号 | 了解 | 本机 `postrotate` 用它让 rsyslog 重开日志(09-15 见过信号表) | 看那个脚本 |
| `sharedscripts` | 了解 | 一组多文件时,脚本**只跑一次** | 读懂 rsyslog 片段 |
| `copytruncate` | 必学 | 复制一份 + 原件**原地清空**,不用喊程序;代价是**那一下可能丢写** | 程序喊不动时用 |
| `/var/lib/logrotate/status` | 必学 | 状态文件,记"上次什么时候转的";读不到会假设空状态 | 为什么 `-d` 结果存疑 |
| `-d`(debug) | 必学 | **预演**:不动文件、不更新状态文件 | 改配置前先看一眼 |
| `-f`(force) | 必学 | **强行真转**,哪怕判断不该转 | 危险,别随手用 |
| `zgrep` / `zcat` / `zless` | 了解 | 不解压就能读 `.gz`(`gunzip` 会消掉 `.gz`,别在 `/var/log/` 随手用) | 查老的日志 |
| **了解层(合并一行)** | 了解 | journal 归 `/etc/systemd/journald.conf`(`SystemMaxUse=`);`prerotate` 是轮转前跑;`nomissingok` 是默认值;`rotate -1` 表示全留 | 见到再查 |
```
旧知识回收(今天暴露出来的)
| 概念 | 一句话 | 出处 |
| ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| **PID ≠ 作业号** | PID = Process ID = **进程号**,系统级唯一;`1` 是作业号,当前 shell 私有、会复用 | 抽考 09-24 第 1 问探底时撞出(09-15 学过) |
| **inode 满** | 一块盘「满」有两种:**数据块满**和 **inode 满**,报同一句 `No space left on device` | 抽考 09-24 第 1 问,先答"PID 满"后补上 |
| **`df` vs `du`** | `df` **问文件系统**(读账本)、`du` **自己走目录树数** | 抽考 09-24 第 2 问,答"跟挂载有关"→ 补讲 |
| **`-s` vs `-S`** | `-s, --summarize`(只出总计)/`-S, --separate-dirs`(不含子目录)------**两个不同选项,大小写要分清** | 抽考 09-24 第 4 问,查了大写 `-S` |
| **`Avail` 的 5%** | 保留块是**比例**(默认 5%,本机约 51 GiB),不是固定的"5G" | 抽考 09-24 第 3 问,答成"5G"→ 当场纠正 |