Linux日志查询实战:高效过滤、时间截取与磁盘应急处理
2026/9/14 5:33:42 网站建设 项目流程

做Linux运维和开发这么多年,日志查询是每天都要干的事。但说实话,大部分人的日志查询水平停留在grep "error" xxx.log这个阶段,出了线上问题只能一行一行翻,运气好几分钟定位,运气不好折腾半小时。这篇博客不打算讲那些人人都知道的基础命令,而是把我这些年实际用过、验证过的日志查询技巧整理一遍——从最基本的按关键词过滤,到按时间段精确截取、多文件关联追踪,再到慢查询日志统计,以及日志挤爆磁盘怎么应急处理,都会拆开讲清楚。

不管你是在用tail -f盯着应用日志排查报错,还是接到“某个接口响应很慢”的工单需要翻MySQL慢查询日志,还是被研发追着要“traceId打印全链路日志”,这篇文章都能给你一套直接能用的思路。内容偏实践,命令我都实测过,直接抄作业就行。

1. 日志查询的整体思路:先定位边界,再动手查

很多人一上来就grep整个大目录,这是效率最低的方式。查询日志前应该先想清楚三件事:查哪台机器、查哪个文件、查什么内容。这三件事想清楚,命令怎么写基本就定了。

1.1 日志文件落在哪里:常见的日志路径与命名规则

不同应用、不同部署方式的日志路径差别很大。常见的几类:

  • Java应用(Spring Boot等):一般通过logbacklog4j2配置,常见路径是/var/log/app//opt/app/logs/或者应用启动目录下的logs/。文件名常见app.logapp-info.logapp-error.log,有的按天滚动叫app-2025-06-10.log,按大小滚动叫app.log.1app.log.2
  • Nginx日志:通常由nginx.confaccess_logerror_log指令控制。路径常见/var/log/nginx/access.log/var/log/nginx/error.log,按天切割后会有access.log-20250610这种带日期的文件。
  • MySQL慢查询日志:由my.cnf里的slow_query_log_file指定,常见/var/log/mysql/mysql-slow.log/var/lib/mysql/目录下。
  • 系统日志:/var/log/messages(CentOS系)、/var/log/syslog(Ubuntu/Debian系),还有/var/log/dmesg看内核启动信息。
  • Docker容器日志:用docker logs <容器名>,日志文件在宿主机的/var/lib/docker/containers/<容器ID>/<容器ID>-json.log

我的习惯是接到需求先ls -lt看目录下最新的日志文件是哪个。因为日志轮转(logrotate)经常会生成带时间戳或数字后缀的文件,如果只看固定的app.log可能根本没查到报错,因为报错已经在滚动后的app.log.1里了。

1.2 查询前的三个关键确认:时间、级别、关键字

具体开查之前,建议先在脑子里过一遍:

  • 时间区间:报错或者异常发生在几点几分?这个决定你是tail看最近内容,还是用sed截取某个时间段的内容,还是zcat去翻历史压缩包。
  • 日志级别:是ERRORWARN还是DEBUG?对应到日志文件可能是不同的文件(很多应用会把 error 单独输出到一个文件),或者是同文件内不同level的混合记录。
  • 搜索关键字:最可能出现的报错关键词或业务唯一标识。比如NullPointerExceptiontimeoutconnection refused,或者更精准的订单号、用户ID、traceId。

注意:日志查询最容易踩的坑是“关键字记忆错误”。比如报错文本实际是Error而你在grep时用了error(区分大小写),结果什么都查不到。稳妥做法是先用一个模糊的小写关键词试探,或者干脆用grep -i忽略大小写。

2. 高频日志查询命令实战:从过滤到截取一次性到位

这一节是全文的核心,每个命令我都标注了适用场景。建议你把这些命令存成一个shell脚本或者做成别名,平时线上排查能省很多时间。

2.1 grep 与 egrep:按关键词过滤日志的最强组合

基础用法grep "关键词" app.log我就不多说了,说几个容易被人忽略的点。

grep -i忽略大小写,适合你不确定日志里是error还是Error的场合。grep -n打印行号,这个在Vim查看时定位特别有用,也方便用sed -n二次截取。grep -C 5(上下文各5行)和grep -A 5/grep -B 5(后5行/前5行)是我实际排查异常时最常用的参数——异常堆栈从来不是一行,只grep "Exception"只能看到第一行,后面的具体报错过程全靠-A带出来。

多个关键词组合过滤,用扩展正则表达式。比如同时匹配timeouttimed out

grep -iE "timeout|timed out" app.log | tail -50

排除干扰关键词,用grep -v。比如你不想看心跳日志(heartbeat),只想看真正的业务报错:

grep -iE "error|exception" app.log | grep -v "heartbeat" | tail -100

这个管道叠加的思路是日志排查的基础,记住一个原则:先用宽泛关键词圈范围,再用排除词缩小范围

2.2 按时间段截取日志:sed 与 awk 的时间区间定位

线上排查最常见的需求就是“上午10:00到10:30之间发生了什么”。如果你只是用grep去搜,会搜出所有时间点出现相同关键词的记录,干扰巨大。这时候应该先按时间段把日志切出来。

假设日志的每一行开头是2025-06-10 10:15:30.123,用sed配合正则做范围匹配:

sed -n '/2025-06-10 10:00:00/,/2025-06-10 10:30:00/p' app.log

这条命令会把10:00:0010:30:00之间的所有行都输出。注意边界问题:如果你的精确时间点在日志里不存在(比如10:00:00.000那行因为并发写入被推迟到了10:00:00.235),sed的起始匹配就不生效。我的技巧是用一个略早的时间点做起点,比如用10:00:00会漏,那就用10:00去匹配,再用headawk进一步处理。

另一个更稳定的做法是用awk按时间字符串比较:

awk '$0 >= "2025-06-10 10:00:00" && $0 <= "2025-06-10 10:30:00"' app.log

前提是日志行首的时间格式统一,且是按字节流顺序写入的(绝大多数应用日志满足这个条件)。awk的做法在日志时间跨分钟、跨小时不均匀时尤其稳定,不会因为边界行缺失而匹配失败。

2.3 tail、head、less 组合:实时跟踪与快速预览

线上监控最常用的还是tailtail -f app.log是持续跟踪文件尾部新写入的内容,适合观察实时输出;如果只想看最近100行用tail -100;如果文件太大,直接tail -f会对磁盘IO有压力,建议配合--pid参数在进程退出时自动结束:tail -f --pid=$!(写进脚本时常用)。

head用得少,但有个场景很有用:日志文件刚切割完,想看新文件的前几行确认格式,head -20 app.log一眼看清时间格式和字段结构,方便后面写awk表达式。

less是日志查询里的隐藏神器。不要只用tail翻完就结束,less app.log打开大文件几乎秒开,支持 Vi 风格快捷键,在文件里用/error向下搜索、?error向上搜索,翻页用PgUp/PgDn,跳到最后用G,回到开头用gg。配合-N显示行号,查找后按n跳到下一处匹配,体验接近用Vim刷日志,但打开大文件比Vim快得多。

2.4 日志统计与按字段聚合:sort、uniq、awk 的正确用法

有时候我们要的不是某一条日志,而是想从日志里看出趋势——某个IP访问了多少次、某个接口报错了多少次、某个用户产生了多少条异常记录。这时候就要用到统计命令组合拳。

统计ERROR日志里每行出现的次数并排序:

grep "ERROR" app.log | sort | uniq -c | sort -rn

这条命令先grep过滤,再sort把相同行聚在一起,uniq -c统计每个相同行的数量,最后的sort -rn按数量从大到小排列。它会输出报错信息和对应的次数,马上就能看出哪个报错是最严重的。不过sort | uniq -c有个前提:相同的行必须相邻,所以sort不能省。

awk按某个字段聚合更高级。比如access.log里通常第1列是客户端IP,想统计每个IP的访问次数:

awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head -20

这个awk脚本先建了一个叫count的关联数组,键是第一个字段(IP),值是该IP出现的次数。所有行处理完后(END),遍历数组打印次数和IP,再交给sort -rn取前20个。同理,如果想统计哪个接口返回了最多的5xx,可以用类似逻辑按第七列(URL)或第八列(状态码)聚合。

3. 场景进阶:多文件检索、traceId链路追踪与慢查询日志分析

基础命令用熟练了,接下来就是实际场景的综合演练。这几个场景我挑的是线上最常遇到的:日志分散在多个文件、一次请求跨多个模块需要串联、接口变慢需要查MySQL慢查询。

3.1 多文件同时检索:find + grep + xargs 的组合套路

生产环境每个模块一个日志目录,报错只告诉你“支付服务有问题”,但支付服务在A机器还是B机器、今天切到哪个目录了,都得靠查。不想一台台登录、一个个文件去看,可以先用find列出符合条件的最新文件:

find /data/logs -name "*.log" -mmin -60

这条会列出60分钟内被修改过的所有.log文件——如果日志按天滚动,大部分情况下你只需要看最新的那个。然后针对这些文件做批量检索:

find /data/logs -name "*.log" -mmin -60 | xargs grep -l "出错了"

grep -l只输出“包含该关键词的文件名”,能快速定位报错到底落在哪个文件。定位到具体文件后,再对该文件做精细化查询。

如果想一次在所有日志文件里搜关键词并显示文件名和行号:

grep -rn "NullPointerException" /data/logs/ --include="*.log" --include="*.out"

-r表示递归扫描/data/logs/目录下所有文件,--include限定文件后缀。这里要警惕大文件递归扫描的压力,在日志文件较多的目录慎用,否则可能把CPU跑满。

3.2 基于traceId的全链路日志串联:一次请求多个日志文件的追踪方法

微服务架构下,一次请求会经过网关、订单服务、支付服务、MQ消费者等多个节点。每跳一个服务,日志文件就换一个。如果每个服务都打印了traceId,你可以以这个Id作为主线把散落的日志“串起来”。

假设某次报错的traceId6aa2526590ad07346b76e2b8d8d80384,先在第一个服务里查:

grep -n "6aa2526590ad07346b76e2b8d8d80384" /data/logs/order/app.log

拿到这个服务打印出的下一跳信息(比如它调用了支付服务、传出的下游traceId),再到支付服务目录里查同一个traceId

grep -n "6aa2526590ad07346b76e2b8d8d80384" /data/logs/pay/*.log

这时候可以用-C 10看上下文,把异常发生前后的业务细节都带出来。我在实际排查中常在多个终端窗口分别跑这几个grep,一边看一边对照时间戳,能很快定位是哪一环超时、哪一环抛异常。

一个非常重要的经验:好记性不如烂笔头。每次排查问题,都建议把traceId、命令、日志文件路径、初步判断整理到一个临时笔记里。别嫌麻烦,很多线上问题的根因是“多个请求之间的traceId被日志框架截断或打印成null”,这时你要顺着时间戳 + 上下游调用关系去找。如果服务没有全链路traceId,只能退而求其次,用“用户ID + 时间区间”作为关联键去各服务日志里交叉检索。

另一个参考做法是日志采集到 Elasticsearch 后用traceId检索。很多团队会在日志采集配置里把traceId映射为 ES 的一个字段,这样就不用登机器grep了。但底层逻辑一样:找一个全局唯一的关联键,把分散的日志串成一条线。

3.3 MySQL慢查询日志:统计分析与可视化看板的底层逻辑

热搜词里有一条“mysql慢查询日志统计分析与可视化看板”,这个方向我做过几次,说说实用做法。

先用MySQL原生功能确认慢查询是否开启:

SHOW VARIABLES LIKE 'slow_query_log%';

如果没开,需要改配置并重启MySQL,或者在当前会话临时开启:

SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2;

long_query_time的单位是秒,2表示超过2秒的SQL才会被记录。

慢查询日志默认是文本格式,一行是SQL的一句描述。统计最常见的慢SQL,可以先用mysqldumpslow工具:

mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log

-s t表示按查询时间排序,-t 10取前10条。它会自动把SQL里的具体数字和字符串替换成N'S',把“长得像”的SQL聚合在一起,这样统计出来的才是“真实的有问题的SQL模式”,而不是被具体参数拆得七零八落。

想看更详细的每次执行时间和扫描行数,直接文本分析:

mysqldumpslow -s al -t 20 /var/log/mysql/mysql-slow.log

这里-s al是按平均锁时间排序,能找出“锁等待严重”的SQL。如果要可视化,可以把慢查询日志解析成 CSV 后导入 Grafana、Quick BI 或自研的看板。解析的方法很简单,就是awk按行匹配# Time:# User@Host:# Query_time:这些字段重新格式化:

awk '/# Query_time/{print $3, $NF}' /var/log/mysql/mysql-slow.log | sort -rn | head -20

这行命令提取出每条慢查询的执行时间和最后一行关键字(通常是SQL语句或部分语句),排序后取前20条就能快速定位“最拖后腿的SQL”。

注意:慢查询日志格式在不同MySQL版本里有差异。MySQL 5.7和8.0的慢查询日志头字段基本一致,但 8.0 默认会用更详细的服务端信息,建议先head -20看一眼格式再写解析脚本。

4. 日志查询的进阶治理:大文件处理、空间释放与问题排查速查

日志查询的技术除了“怎么查”,还包括“查的时候遇到的各种环境问题”——最典型的是日志文件大到几GB打不开、日志把磁盘写满导致服务挂掉、删除日志文件后空间不释放。这些问题不定时来一次,遇上了就要立刻处理。

4.1 超大日志文件的读取策略:less、zcat、split 的组合用法

日志文件超过1GB甚至10GB时,vim直接打不开,cat会把终端刷爆。这时候你要学会“分段读”。

less是最友好的选择,因为它是按需读取文件块,不会把整个文件加载进内存,1GB 的日志也能秒开。操作方式和前面说的一样,/关键词搜索,G跳末尾,gg跳到开头。

如果是滚动压缩过的文件,用zcat查看:

zcat app.log.20250610.tar.gz

zcat会把.gz压缩文件解压后输出到标准输出,可以直接接管道过滤。比如查压缩日志中的报错:

zcat app.log.20250610.tar.gz | grep "ERROR" | head -50

如果压缩包内文件很多且彼此独立,更推荐zgrep

zgrep "ERROR" app.log.20250610.tar.gz

它相当于grep的压缩版,不用解压整个文件就能检索,适合多文件快速定位。

还有一种场景:日志文件太大想按行数切分,方便携带或交给别的工具分析:

split -l 50000 app.log part_

这条命令会把app.log按每5万行切成多个文件,生成part_aapart_ab……然后用grep分别查。我一般只在本地分析时用,线上不建议对正在写入的日志做split,因为切割期间文件仍在增长,结果不可控。

4.2 日志挤爆磁盘的应急处理:du、lsof,以及WSL删除文件后空间不释放问题

热搜词有一条“wsl linux删除文件后空间没释放”,这个问题在传统Linux服务器上也会出现:你明明rm删了文件,df -h一看磁盘占用率还是100%。原因是某个进程仍持有该文件的文件句柄,文件在文件系统里虽然被“删除”了,但进程还在写或读,占用的空间要等进程关闭文件句柄才会真正释放。

排查步骤:

df -h

先看哪个分区满了。然后:

lsof | grep deleted

lsof列出所有打开的文件,grep deleted直接过滤出“已删除但仍有进程占用的文件”。找到占用文件的进程名和PID后,重启该进程或让应用重新加载日志文件,空间才会释放。

如果是WSL(Windows Subsystem for Linux)场景,除了lsof外还可能是VHD虚拟磁盘文件没有自动压缩。WSL的整个文件系统都存放在Windows下的一个虚拟磁盘(.vhdx)里,你在WSL内删除文件,磁盘里虽然腾出了可用块,但.vhdx文件本身不会自动变小。需要手动压缩:

wsl --shutdown # PowerShell里执行 Optimize-VHD -Path "C:\Users\你的用户名\AppData\Local\Packages\...\ext4.vhdx" -Mode Full

Optimize-VHD是Windows自带的Hyper-V模块命令,需要管理员权限。压缩前必须确保WSL完全关闭,否则会报错。这个经验我踩过坑,分享出来特别提醒:WSL删文件后空间不释放,先查lsof,确认没有进程占用后再执行VHD优化,顺序不能反。

4.3 日志查询常见问题排查速查表

日志查询会遇到的坑很多,我整理了一张速查表,都是实际工作中验证过的:

现象可能原因排查方法
grep 搜不到关键词,但日志里明明有关键词大小写不匹配,或日志在滚动后的旧文件里grep -i;用ls -lt查看最新滚动文件
tail -f 不输出内容文件在滚动切割,tail 跟踪的是旧的 inodetail -F(大写F)自动跟踪文件重新创建
awk 时间区间截取为空时间边界处没有精确匹配行起止时间稍微放宽,或用>=比较而非精确匹配
日志报错太多,不知道重点只看了报错行没看堆栈上下文grep -A 20把堆栈一次性带出
日志里有乱码或中文显示异常文件编码不是UTF-8file app.log查编码,用iconv -f GBK -t UTF-8转换
磁盘满了但找不到大文件大文件被删除但进程占用,或日志文件在别的挂载点lsof | grep deleteddf -h确认分区再定位
查询历史压缩日志找不到入口.tar.gz.gz文件不能直接 grepzgrepzcat检索
访问日志按IP统计不准中间有代理或负载均衡,真实IP在X-Forwarded-For调整 awk 或 grep 的字段位置,取代理头里的IP

这张表不是死的,不同团队、不同应用的日志格式差异很大,但排查思路是一致的:先看格式、再看时间、最后精确定位。

4.4 把“查日志”做成日常习惯:别名、脚本和轮转策略

日志查询不只用于排查故障,还可以做成日常巡检。我常用两个技巧:

第一个是给高频命令做别名。在~/.bashrc~/.zshrc里加上:

alias errgrep='grep -iE "error|exception"' alias logtail='tail -f -n 100' alias slowlog='mysqldumpslow -s t -t 10'

这样平时敲命令能省几秒,连续排查时效率提升很明显。

第二个是把日志轮转配好。日志文件只涨不删,再大的磁盘也会满。Linux 自带的logrotate是最省事的轮转工具。在/etc/logrotate.d/下创建应用对应的配置:

/data/logs/app/*.log { daily rotate 7 compress delaycompress missingok notifempty sharedscripts postrotate /bin/kill -USR1 `cat /var/run/app.pid` endscript }

这段配置的含义是:每天轮转一次,保留7份历史日志,历史日志压缩为.gz,轮转后主动给应用进程发USR1信号让它重新打开日志文件。delaycompress表示轮转后第一份不立即压缩,因为下一条日志可能还在写。日志轮转配好后,查日志时看到-YYYYMMDD.gz后缀的旧文件,基本上可以放心判断它不会再增长,也能安心用zgrep去查。

5. 个人经验与建议:日志查询的本质是“快速定位问题”,而不是“背命令”

我遇到过很多同事把日志查询当成“背命令”的活,觉得会greptailawk就够用了。实际用下来,真正拉开差距的是你有没有“日志思维”——日志只是系统在关键时刻留下的脚印,你的任务是顺着脚印推断当时发生了什么。

我的建议是先练熟最常用的5个组合:grep + -A/-B看上下文、sed/awk按时间截取、tail -F实时跟踪滚动文件、sort + uniq -c做频率统计、find + xargs + grep做跨文件检索。这5个组合覆盖了我日常80%的排查场景。再往深走,可以去研究jq处理JSON格式日志、lnav做日志文件交互式浏览、grok规则做日志字段提取,这些工具能在特定场景里把效率再拉高一个档次。

最后再分享一个实操细节:查日志时,时间戳格式一定要确认清楚。有的应用打印的是2025-06-10 10:00:00,有的只有10:00:00,还有的是Unix时间戳(毫秒级13位)。如果拿带日期的字符串去awk比较,而日志只有时分秒,必然匹配不上。遇到Unix时间戳,先换算成可读时间再写过滤条件,或者直接用date -d @1720000000做转换。这个小细节看着不起眼,实际排查中能卡掉一大半人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询