做Linux运维和开发这么多年,日志查询是每天都要干的事。但说实话,大部分人的日志查询水平停留在grep "error" xxx.log这个阶段,出了线上问题只能一行一行翻,运气好几分钟定位,运气不好折腾半小时。这篇博客不打算讲那些人人都知道的基础命令,而是把我这些年实际用过、验证过的日志查询技巧整理一遍——从最基本的按关键词过滤,到按时间段精确截取、多文件关联追踪,再到慢查询日志统计,以及日志挤爆磁盘怎么应急处理,都会拆开讲清楚。
不管你是在用tail -f盯着应用日志排查报错,还是接到“某个接口响应很慢”的工单需要翻MySQL慢查询日志,还是被研发追着要“traceId打印全链路日志”,这篇文章都能给你一套直接能用的思路。内容偏实践,命令我都实测过,直接抄作业就行。
1. 日志查询的整体思路:先定位边界,再动手查
很多人一上来就grep整个大目录,这是效率最低的方式。查询日志前应该先想清楚三件事:查哪台机器、查哪个文件、查什么内容。这三件事想清楚,命令怎么写基本就定了。
1.1 日志文件落在哪里:常见的日志路径与命名规则
不同应用、不同部署方式的日志路径差别很大。常见的几类:
- Java应用(Spring Boot等):一般通过
logback或log4j2配置,常见路径是/var/log/app/、/opt/app/logs/或者应用启动目录下的logs/。文件名常见app.log、app-info.log、app-error.log,有的按天滚动叫app-2025-06-10.log,按大小滚动叫app.log.1、app.log.2。 - Nginx日志:通常由
nginx.conf的access_log和error_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去翻历史压缩包。 - 日志级别:是
ERROR、WARN还是DEBUG?对应到日志文件可能是不同的文件(很多应用会把 error 单独输出到一个文件),或者是同文件内不同level的混合记录。 - 搜索关键字:最可能出现的报错关键词或业务唯一标识。比如
NullPointerException、timeout、connection 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带出来。
多个关键词组合过滤,用扩展正则表达式。比如同时匹配timeout或timed 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:00到10:30:00之间的所有行都输出。注意边界问题:如果你的精确时间点在日志里不存在(比如10:00:00.000那行因为并发写入被推迟到了10:00:00.235),sed的起始匹配就不生效。我的技巧是用一个略早的时间点做起点,比如用10:00:00会漏,那就用10:00去匹配,再用head或awk进一步处理。
另一个更稳定的做法是用awk按时间字符串比较:
awk '$0 >= "2025-06-10 10:00:00" && $0 <= "2025-06-10 10:30:00"' app.log前提是日志行首的时间格式统一,且是按字节流顺序写入的(绝大多数应用日志满足这个条件)。awk的做法在日志时间跨分钟、跨小时不均匀时尤其稳定,不会因为边界行缺失而匹配失败。
2.3 tail、head、less 组合:实时跟踪与快速预览
线上监控最常用的还是tail。tail -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作为主线把散落的日志“串起来”。
假设某次报错的traceId是6aa2526590ad07346b76e2b8d8d80384,先在第一个服务里查:
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.gzzcat会把.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_aa、part_ab……然后用grep分别查。我一般只在本地分析时用,线上不建议对正在写入的日志做split,因为切割期间文件仍在增长,结果不可控。
4.2 日志挤爆磁盘的应急处理:du、lsof,以及WSL删除文件后空间不释放问题
热搜词有一条“wsl linux删除文件后空间没释放”,这个问题在传统Linux服务器上也会出现:你明明rm删了文件,df -h一看磁盘占用率还是100%。原因是某个进程仍持有该文件的文件句柄,文件在文件系统里虽然被“删除”了,但进程还在写或读,占用的空间要等进程关闭文件句柄才会真正释放。
排查步骤:
df -h先看哪个分区满了。然后:
lsof | grep deletedlsof列出所有打开的文件,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 FullOptimize-VHD是Windows自带的Hyper-V模块命令,需要管理员权限。压缩前必须确保WSL完全关闭,否则会报错。这个经验我踩过坑,分享出来特别提醒:WSL删文件后空间不释放,先查lsof,确认没有进程占用后再执行VHD优化,顺序不能反。
4.3 日志查询常见问题排查速查表
日志查询会遇到的坑很多,我整理了一张速查表,都是实际工作中验证过的:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| grep 搜不到关键词,但日志里明明有 | 关键词大小写不匹配,或日志在滚动后的旧文件里 | 用grep -i;用ls -lt查看最新滚动文件 |
| tail -f 不输出内容 | 文件在滚动切割,tail 跟踪的是旧的 inode | 用tail -F(大写F)自动跟踪文件重新创建 |
| awk 时间区间截取为空 | 时间边界处没有精确匹配行 | 起止时间稍微放宽,或用>=比较而非精确匹配 |
| 日志报错太多,不知道重点 | 只看了报错行没看堆栈上下文 | 用grep -A 20把堆栈一次性带出 |
| 日志里有乱码或中文显示异常 | 文件编码不是UTF-8 | 用file app.log查编码,用iconv -f GBK -t UTF-8转换 |
| 磁盘满了但找不到大文件 | 大文件被删除但进程占用,或日志文件在别的挂载点 | lsof | grep deleted;df -h确认分区再定位 |
| 查询历史压缩日志找不到入口 | .tar.gz或.gz文件不能直接 grep | 用zgrep或zcat检索 |
| 访问日志按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. 个人经验与建议:日志查询的本质是“快速定位问题”,而不是“背命令”
我遇到过很多同事把日志查询当成“背命令”的活,觉得会grep、tail、awk就够用了。实际用下来,真正拉开差距的是你有没有“日志思维”——日志只是系统在关键时刻留下的脚印,你的任务是顺着脚印推断当时发生了什么。
我的建议是先练熟最常用的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做转换。这个小细节看着不起眼,实际排查中能卡掉一大半人。