先看结论:不是二选一
在服务器上排查日志,工具选择取决于三件事:环境是否允许安装 rg、日志是单个大文件还是目录、是否需要持续跟踪。grep 的优势是几乎无处不在,适合管道、脚本、老系统和只读环境;rg 的优势是递归搜索时默认并行、默认输出更适合阅读,并且有一套自己的忽略规则。问题也在这里:rg 默认会跳过隐藏文件、被 ignore 规则排除的文件和二进制文件,直接用它替换 grep -R 可能漏日志。搜索大日志前先缩小范围,比换工具更重要。
限定:下面命令以 GNU grep 和 ripgrep 的常见行为为例;不同发行版、BusyBox grep、BSD grep 的选项可能有差异,上线前用 grep --help、rg --help 核对。
场景一:单个大文件,只找错误行
先明确日志级别字段。不要只搜小写 error,因为很多日志会同时出现 error_code。先按实际格式选模式:
# grep:行号 + 文本模式,适合容器/老服务器
grep -n -a -E 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
# rg:同样按行输出,默认会显示行号;这里显式写出 -n
rg -n -a -e 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
-a 的作用是把文件当文本处理。日志里偶尔出现 NUL 字节时,grep 可能只提示 Binary file matches,rg 可能直接跳过;加 -a 可以避免这种“看起来没结果”的情况。若日志确定是纯文本,可以不加。
如果只想看前若干条,不要直接 | head 截断上下文组,优先限制匹配条数:
grep -m 50 -n -a -E 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
rg -m 50 -n -a -e 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
-m 是每个文件最多输出多少条匹配行;不同实现细节可能不同,但用于快速抽样足够。需要固定字符串而不是正则时,grep 用 -F,rg 也用 -F;需要忽略大小写时两者都用 -i。例如:
grep -inF 'connection refused' /var/log/app/app.log
rg -inF 'connection refused' /var/log/app/app.log
场景二:保留前后上下文
排查报错通常不能只看一行。两者都支持 -A(后)、-B(前)、-C(前后):
grep -n -a -C 3 -E 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
rg -n -a -C 3 -e 'ERROR|Exception|FATAL|panic' /var/log/app/app.log
输出很多时,分页查看并保留颜色:
grep -n -a --color=always -C 3 -E 'ERROR|Exception|FATAL|panic' /var/log/app/app.log | less -R
rg -n -a --color=always -C 3 -e 'ERROR|Exception|FATAL|panic' /var/log/app/app.log | less -R
--color=always 是为了让颜色经过管道后仍然保留,less -R 负责解释颜色控制符。若只想看匹配行和文件名,不需要颜色,去掉即可。脚本解析时不要依赖颜色和分组标题,grep 用 --color=never,rg 用 --no-heading --color=never。
场景三:目录里递归搜索,排除归档和依赖目录
日志目录常混有归档、压缩包、应用代码或依赖目录。grep 的递归搜索要显式限制文件类型和排除目录:
grep -r -n -a --include='*.log' --exclude-dir=archive --exclude-dir=node_modules --exclude-dir=.git -E 'ERROR|Exception|FATAL|panic' /var/log/app
这里用 -r 而不是 -R:-r 递归目录,-R 会跟随所有符号链接。日志目录如果有指向别处的符号链接,需要先确认是否要跟。--include='*.log' 只收日志文件,避免把压缩包和二进制拖进来。多个排除目录就写多个 --exclude-dir,比把花括号放进引号更稳。压缩日志如 .gz 不会被普通 grep 或 rg 直接解压,需要先解压或改用 zgrep 一类工具。
rg 默认就是递归,并且默认遵守 .gitignore、跳过隐藏文件和二进制文件。对日志目录,这个默认行为可能帮倒忙:
rg -n -a --no-ignore --hidden -g '*.log' -g '!archive/**' -g '!node_modules/**' -g '!.git/**' -e 'ERROR|Exception|FATAL|panic' /var/log/app
--no-ignore 让 rg 不因 ignore 文件跳过路径,--hidden 让隐藏目录也进入搜索;一旦打开隐藏文件,就要用 -g '!.git/**' 这类规则把无关目录排掉。-g '*.log' 把搜索范围限制在 .log 文件。若日志文件名没有 .log 后缀,就不要加这个 glob,或者改成实际后缀。不要把 -uuu 当成万能加速开关:它会同时放开忽略规则、隐藏文件和二进制过滤,容易把无关文件带进来,输出也更难读。
场景四:正在写入的日志用流式管道
直接搜索一个正在被追加的大文件,只能看到搜索时刻已经写入的内容,不会持续跟踪。要实时看新报错,用 tail -f 接 grep 或 rg:
tail -f /var/log/app/app.log | grep --line-buffered -n -a -E 'ERROR|Exception|FATAL|panic'
tail -f /var/log/app/app.log | rg --line-buffered -n -a -e 'ERROR|Exception|FATAL|panic'
--line-buffered 让输出按行刷新,否则管道缓冲可能让你觉得“没动静”。注意这里的行号是相对于 tail 输出流的,不是原文件里的绝对行号。若需要原文件行号,应直接搜索文件,或者用能保留行号的日志工具;不要拿流式行号去和编辑器行号对照。
如果日志已经轮转,先确认当前文件和归档文件的时间范围,再决定搜哪一个。不要一上来对整个 /var/log 递归;先锁定服务目录和文件名,能显著减少读盘量。这个建议对 grep 和 rg 都成立。
一张对比表
| 目标 | grep 写法 | rg 写法 | 注意 |
|---|---|---|---|
| 单文件找错误行 | grep -n -a -E 'ERROR|Exception' app.log |
rg -n -a -e 'ERROR|Exception' app.log |
两者都要按日志格式调整模式 |
| 保留上下文 | grep -C 3 ... |
rg -C 3 ... |
输出多时接 less -R |
| 限制条数 | grep -m 50 ... |
rg -m 50 ... |
用于抽样,不要依赖截断后的上下文 |
| 递归目录 | grep -r --include='*.log' ... |
rg -g '*.log' ... |
rg 默认忽略规则要检查 |
| 排除目录 | --exclude-dir=archive |
-g '!archive/**' |
多个目录写多条规则 |
| 实时跟踪 | tail -f ... | grep --line-buffered |
tail -f ... | rg --line-buffered |
行号是流内行号 |
| 脚本解析 | --color=never |
--no-heading --color=never |
不要解析颜色和分组标题 |
表格里的 ... 代表前文的模式与文件路径;实际执行时替换成完整参数。
怎么选:按环境而不是按喜好
如果目标机器是生产容器、最小化系统、老发行版,或者你只能通过跳板机执行只读命令,优先 grep。它不需要额外安装,管道行为稳定,适合写进一次性排查脚本。若日志目录层级多、文件多,并且机器上已经有 rg,可以用 rg 先做递归筛选;但在日志目录里要显式处理 --hidden、--no-ignore 和 -a,否则默认规则可能让结果不完整。
单个大文件上,rg 不一定比 grep 快很多:并行搜索更依赖文件数量,而不是一个文件的体量;两者都要把文件读完。真正省时间的是先缩小时间窗口、限制文件后缀、排除归档目录,再限制输出条数。把 grep ERROR /var/log 换成 rg ERROR /var/log 不是优化,只是换了一个默认规则更多的工具。
一个可复用的排查顺序是:先 ls -lh 和 tail -n 确认日志大小与最新内容;再用带 -n 和 -a 的 grep 或 rg 找错误行;需要因果链时加 -C 3;目录搜索时加文件类型和排除规则;实时观察时用 tail -f 加 --line-buffered。工具可以替换,这个顺序不要省。