
干后端和运维这些年我最大的感受就是linux命令学得再花哨到最后每天高频使用的还是看日志那一套。不管是接口突然报 500还是半夜被告警吵醒你第一个动作永远是 ssh 上机器然后 tail、grep、less 三连。日志这东西平时没人惦记一旦出问题它就是现场唯一的目击证人。这篇内容不打算把命令手册抄一遍而是拿实际排查场景来讲日志放在哪、怎么看实时输出、怎么从大海捞针一样找关键行、日志轮转压缩了怎么处理、以及一次线上故障里这些命令是怎么串起来的。适合刚接触 Linux 的开发者也适合那些会用 tail -f 但遇到复杂日志场景就开始翻文档的同学。1. 拿到一台机器先认清日志都放在哪很多人栽跟头不是不会敲命令而是不知道日志文件在哪个目录。你连路径都没找对后面的一切命令都是空谈。1.1 系统日志和业务日志的常见落盘位置绝大多数 Linux 发行版都遵循 FHS 文件系统规范把系统日志放在/var/log下面。但不同发行版的命名有差异比如 CentOS、RHEL 系列通常用/var/log/messages记录系统整体运行信息而 Ubuntu、Debian 系列用/var/log/syslog。你在这两个文件里能看到用户登录、计划任务执行、内核打印、服务启动失败这类“大杂烩”日志。我平时排查时第一步永远是先列目录ls -lht /var/log | head -20-l显示详细信息-h把文件大小转成人能读的 K/M/G-t按时间排序。前面加head -20只取前 20 行目的是快速看最近有哪些日志文件在更新。这个动作能帮你判断系统当前最活跃的日志源是什么。更常见的几类固定日志文件文件路径典型内容/var/log/nginx/access.logNginx 访问日志每行一条请求/var/log/nginx/error.logNginx 运行错误记录/var/log/mysql/error.logMySQL 启动、运行、错误信息/var/log/mysql/slow.logMySQL 慢查询日志/var/log/secureCentOS/RHEL 系登录验证日志/var/log/auth.logUbuntu/Debian 系登录验证日志/var/log/croncron 定时任务执行记录注意/var/log/secure和/var/log/auth.log这类登录日志没有 root 权限的话通常打不开读取时一般要加sudo。还有两个特殊文件/var/log/wtmp和/var/log/btmp是二进制格式不能直接cat看需要用last查看成功登录记录、lastb查看失败登录记录。业务应用日志就五花八门了。很多 Java 应用放在/opt/app/logs或/data/logs下Python、Go 服务习惯写在进程启动参数指定的路径。这类自定义路径没有统一规律建议拿到一个项目后先看启动脚本或配置文件里的 log 相关配置把路径记住。更省事的办法是看 systemd 服务如果应用是用 systemd 管理的先执行这个systemctl status 服务名输出里会显示服务的工作目录和执行命令顺着这个线索找日志路径很稳。1.2 systemd journal 是另一套日志体系新版 Linux 系统普遍用 systemd 接管服务管理同时也接管了部分日志记录。通过journalctl可以查看某个服务的标准输出和标准错误它的好处是日志集中存储、按服务隔离、自带时间过滤能力。查看某个服务的日志journalctl -u nginx只看最近一小时的错误日志journalctl -u nginx --since 1 hour ago --priority err--priority err等价于只显示错误及更严重级别的日志。这个优先级体系很有意思日志级别从 debug、info、notice、warning、err、crit、alert 到 emerg 依次升高你指定了err之后普通 info 日志全被过滤掉只有错误级别以上的才会留下。定位问题时这种过滤非常高效因为大部分服务在正常运行时的 info 日志量巨大错误信息早被淹没了。不过 journald 日志默认存在内存或环形缓冲里如果没有配置Storagepersistent系统重启后历史日志会丢。所以我自己在线上排查时还是更依赖落盘的文本日志文件journalctl 主要用来快速看某个服务最近的状态变化。2. 实时跟踪和翻页阅读tail/less 才是永不下岗的主力一旦确定日志路径接下来就是怎么看了。很多新手习惯直接vi打开日志文件这在小文件上没问题但一旦文件到了几百 MBvi 会先把整个文件加载进内存打开过程极慢操作还容易卡死。这时候正确的主力工具是tail和less。2.1 tail追文件尾巴的正确姿势日志是追加写入的最新内容永远在文件末尾所以tail天然适合看日志。查看最后 100 行tail -n 100 /var/log/nginx/access.log-n表示行数不写默认是 10 行。想同时看多个日志文件可以这样tail -n 20 /var/log/nginx/error.log /var/log/myapp/app.log如果要在日志持续写入时实时跟踪用-ftail -f /var/log/myapp/app.log-f会保持进程不退出日志有新行写入时立刻打印到终端。排查线上问题时的标准操作是先开一个窗口执行tail -f再另开一个窗口复现请求或等待报错发生终端里就能实时看到日志刷出来。这里必须多说一句-f和-F的区别。-f跟踪的是文件描述符如果日志文件被 logrotate 重命名然后新建了一个同名文件tail -f会继续读那个已经被改名的旧文件表面上好像日志停了。而-F会根据文件名重新打开文件即使轮转后也不怕。我在生产环境几乎只用tail -Ftail -F /var/log/myapp/app.log这个细节是排坑重点后面第 6 章我还会专门讲。2.2 less在几百 MB 日志里翻页不卡的秘诀less是我最爱的日志查看命令没有之一。它不会一次性把整个文件读进内存而是按需加载当前屏幕要显示的内容所以打开多大的文件都不慌。常用打开方式less -N -S /var/log/myapp/app.log-N显示行号方便后面引用具体行-S让超长的行不自动换行而是水平滚动这对查看堆栈日志特别友好日志里的人眼可读性会好很多。进入 less 之后的核心操作按键功能G跳到文件末尾看最新日志g回到文件开头/关键词向下搜索关键词?关键词向上搜索关键词n/N下一个匹配 / 上一个匹配PgUp/PgDn翻页q退出看到堆栈或者长 SQL 时先用/搜索关键字定位再用方向键左右移动查看超长内容这个体验比任何图形化工具都顺。less 还有一个很多人不知道的跟综模式直接按ShiftF效果等同于tail -f日志有新行会自动滚动。想退出跟综按CtrlC回到普通浏览模式然后按q退出。这个功能的好处是它既能像tail一样跟综又能随时停下来往上翻页看上下文比单独用 tail 灵活得多。2.3 head 和 cat 不是没用只是要分场合head和cat也是常用的但要分清使用场景。cat适合文件很小、需要完整浏览的情况比如看配置文件、看小体积的日志片段。但拿cat去看一个 2GB 的日志文件终端会直接刷爆而且为了渲染输出消耗大量 CPU 和 IO甚至会拖慢服务器这是一个典型的“用错工具”现场。head通常用来查看文件开头的内容确认日志格式head -n 20 /var/log/myapp/app.log有时只想看文件已经写入的字节数前一部分可以用-c按字节切head -c 1M /var/log/myapp/app.log这条命令可以快速取出文件前 1MB 内容确认日志的字段结构、时间格式、日志级别分布。需要注意的是head -c可能把某一行从中间截断这没问题你只是想看格式而已不需要完整行。生产环境里把 tail、less、grep 组合起来是最高频的操作后面第 3 章会专门展开 grep 相关的内容。3. 从海量日志里精准捞针grep/sed/awk 三板斧整天tail -f是低效的。日志量大的时候几分钟就能刷出几千行靠肉眼盯是盯不过来的。这时你需要的是“过滤”和“提取”能力核心工具链是 grep、sed、awk。3.1 grep先解决“有没有”再看“在哪”grep 是日志排查里使用频率最高的命令基本功能是按行匹配关键词并输出。查看包含 ERROR 的所有行并显示行号grep -n ERROR /var/log/myapp/app.log不区分大小写grep -i error /var/log/myapp/app.log同时匹配多个关键词用扩展正则grep -E ERROR|FATAL|Exception /var/log/myapp/app.log排除掉干扰信息比如健康检查请求grep -v healthcheck /var/log/myapp/app.log只看匹配到了多少行grep -c ERROR /var/log/myapp/app.log我实际排障时最常用的是-B和-A它们能在匹配行的前后带上指定行数的上下文。比如请求超时日志本身没什么意义但超时前一行往往记录着某个服务的调用链信息这时这样查grep -B 5 -A 20 DB_TIMEOUT /var/log/myapp/app.log-B 5表示匹配行前 5 行-A 20表示匹配行后 20 行。这条命令能让你把“异常现场”完整提取出来而不是只看到孤零零一个错误。处理多个日志文件时可以用-l列出包含关键词的文件名先缩小范围grep -l 订单接口超时 /var/log/myapp/*.log找到文件后再去对应文件里详细看。还有一个提升效率的细节grep -F。如果你搜索的字符串包含大量正则特殊字符比如a.b*不转义的话会被当成正则解释结果完全不对。用-F表示“固定字符串”匹配原样查找省去转义烦恼grep -F OrderServiceImpl.batchCreate /var/log/myapp/app.log3.2 sed按行号和时间范围切出一段日志grep 是按关键词捞但如果你的需求是“把某段时间内的所有日志都取出来”关键词就不管用了因为这段时间里的日志什么内容都有。这时用 sed 更合适。按行号提取从第 100 行到第 200 行sed -n 100,200p /var/log/myapp/app.log-n表示关闭默认输出p表示打印匹配范围的行。这个命令在日常调试中可以用来快速定位某个时间段对应的日志区域。更实用的场景是按时间范围提取。假设日志第一列是2025-06-15 14:30:00这种格式想提取 14:30 到 14:35 之间所有日志sed -n /2025-06-15 14:30:00/,/2025-06-15 14:35:00/p /var/log/myapp/app.log这个写法的含义是从匹配到开始时间的行开始打印一直打印到匹配到结束时间的行。需要注意两点一是开始时间和结束时间的字符串必须在日志里真实存在二是如果这段时间内没有日志写入或者结束时间字符串在开始之前出现结果会出乎意料所以最好先grep确认两个时间点都有匹配行。把提取出来的内容接到管道里继续处理是更高级的用法sed -n /2025-06-15 14:30:00/,/2025-06-15 14:35:00/p /var/log/myapp/app.log | grep -E ERROR|WARN | less3.3 awk提取字段和快速统计awk 可以理解为“按列处理文本”的工具。日志基本都是结构化的通用格式里空格分列Nginx 访问日志默认用空格分列其中 IP 在第一列、HTTP 状态码在第九列左右。提取每行的第一列和最后一列awk {print $1, $NF} /var/log/nginx/access.log$1是第一列$NF是最后一列NF是每行字段数量的内置变量所以$NF就表示最后一个字段。对新手来说awk 最熟悉的场景就是这种“取出几个字段看看”。按响应时间过滤比如取出耗时超过 3 秒的请求awk $NF 3000 {print $1, $NF} /var/log/nginx/access.log要提前确认最后一列是请求耗时。更强大的统计能力体现在配合数组和END块。统计访问量最多的前 10 个 IPawk {count[$1]} END {for (ip in count) print ip, count[ip]} /var/log/nginx/access.log | sort -rn | head -10这个命令拆开理解count[$1]用 IP 做数组下标累加END块在所有行处理完后执行把每个 IP 的计数打印出来最后用sort -rn按数值倒序排列head -10取前 10 名。整个链条在十几秒内就能统计完一个大访问日志文件比用 Excel 打开半天实用太多。如果只想统计有多少个不同 IPawk {ips[$1]1} END {print length(ips)} /var/log/nginx/access.logawk 的功能远不止这些但对于日志排错来说掌握字段提取、条件过滤、分组统计这三点就足够应付大多数场景了。4. 日志轮转压缩后怎么查zcat 一族和文件顺序如果你只是看当前的最新日志tail -F够了。但很多时候问题发生在昨天、前天文件早就被 logrotate 轮转走了甚至已经被 gzip 压缩。这时候直接tail压缩文件会得到一堆乱码正确打开方式得换。4.1 logrotate 会生成哪些文件Linux 系统一般通过 logrotate 定期切割日志文件避免单个文件无限膨胀。切割规则写在/etc/logrotate.conf和/etc/logrotate.d/里。典型的切割结果可能是这样的app.log app.log.1 app.log.2.gz app.log.3.gz越靠后的文件越旧。app.log是最新正在写的文件app.log.1是上一轮的旧日志app.log.2.gz是上上轮且已经压缩过的日志。有些配置会生成带日期后缀的文件app.log app.log-2025-06-14.gz app.log-2025-06-13.gz看到这种命名基本就明白文件的新旧关系了。另外 logrotate 配置里如果设了delaycompress新切割出来的.1文件可能还是未压缩的文本要等下一轮轮转才会被压缩。所以查看历史日志时不要假设所有非当前文件都是.gz最好先ls看一遍再动手。4.2 压缩日志的查看命令碰到.gz结尾的日志文件不能直接cat或grep需要先解压或使用支持压缩格式的专用命令。最常用的是这组zcat app.log.2.gz | head -50zcat的行为等价于“解压并输出到标准输出”所以后面可以接管道继续处理。从压缩日志里搜索关键词zgrep -n ERROR /var/log/myapp/app.log.2.gzzgrep和grep的参数基本通用包括-A、-B、-i、-E只是它内部会自动解压再匹配使用体验和无压缩日志几乎一样。分页查看压缩日志zless /var/log/myapp/app.log.2.gzzless在查看超大的压缩日志时很实用不用把整个文件解压到磁盘而是边解压边加载。如果你的日志压缩格式是.xz对应的是xzcat、xgrep、xzless。如果是运维手工打包的.zip可以用unzip -p直接把内容输出到管道unzip -p app.zip | grep ERROR-p表示输出到标准输出而不是真的解压出文件非常适合快速搜索打包日志里的关键内容。4.3 跨文件提取某段时间日志的顺序问题当问题跨越了轮转切割的时间点比如想查 14:30 到 14:35 的日志而这 5 分钟的日志一部分在app.log一部分在app.log.1你就需要按时间顺序把多个文件串起来一起查看。如果文件都是普通文本未压缩cat /var/log/myapp/app.log.1 /var/log/myapp/app.log | grep 2025-06-15 14:3注意顺序是从旧到新先读app.log.1再读app.log这样输出顺序和真实时间顺序一致。如果历史文件是 gzip 压缩的需要把 zcat 和 cat 混用zcat /var/log/myapp/app.log.2.gz /var/log/myapp/app.log.3.gz cat /var/log/myapp/app.log.1 /var/log/myapp/app.log -第二种写法是先用zcat把旧的压缩日志解压输出再cat前面的普通文件和当前文件管道最后的-表示从标准输入读取数据也就是接住zcat输出的内容。这样把所有文件拼接成完整的时间流。这里有个提醒如果你直接cat了一个.gz文件终端会刷出乱码千万不要慌CtrlC停掉就好日志文件本身不会被破坏。更省事的方式是如果所有历史文件都是.gz直接对多个文件使用zgrepzgrep -h ERROR /var/log/myapp/app.log.2.gz /var/log/myapp/app.log.3.gz-h参数让 zgrep 不输出文件名前缀输出干净地只有匹配行。不过它输出的顺序是按你给的参数顺序来的所以依然要注意文件新旧排列。5. 一次线上超时故障我的完整日志排查流程命令分开讲都会用但真正能解决问题的还是组合使用。这里我把一次典型的线上故障排查过程完完整整列出来你看看这些命令是怎么一环扣一环的。5.1 故障现象与第一直觉下午 2 点 10 分左右业务方反馈用户下单超时接口响应时间从平时的 200ms 飙到 5 秒以上同时数据库连接池告警。服务器 CPU 看着不高内存也正常初步判断问题不在硬件资源上。这次排查我执行的第一组命令是ls -lht /var/log/myapp/ | head -10 date第一条看日志目录里哪些文件在更新第二条确认服务器当前时间。为什么要先看时间因为后续所有日志分析都要和当前时间做对比如果服务器时间本身漂了后面的排查就会跑偏。这步虽然简单但很多人会漏掉。结果发现app.log的最后修改时间停留在 5 分钟前这说明应用可能已经写不进日志了或者日志轮转出了问题或者应用进程已经不响应了。这本身就是一个重要信号。5.2 从应用日志到数据库慢查询的排查链路继续看应用日志的尾部内容tail -n 100 /var/log/myapp/app.log看到大量Thread pool exhausted和DB_TIMEOUT。线程池耗尽通常意味着请求被阻塞而阻塞根源可能在下游数据库。先统计一下DB_TIMEOUT出现的次数并快速看它按秒的分布grep -c DB_TIMEOUT /var/log/myapp/app.log grep DB_TIMEOUT /var/log/myapp/app.log | awk {print $2} | cut -c1-8 | sort | uniq -c第二个命令把时间段的分钟信息提取出来统计每个分钟内超时次数。$2如果存的是HH:MM:SS时间字段cut -c1-8截出时分秒如果只看分钟级别就截1-5。看到结果后就能判断超时是持续存在还是突然爆发。这次的结果是 14:00 之前完全没有14:00 之后突然增加说明问题是从某个时刻开始触发的。随后提取一条超时记录附近的上下文看它具体卡在哪个环节grep -B 5 -A 20 DB_TIMEOUT /var/log/myapp/app.log | less -N从上下文里发现每次超时前都有一个 SQL 查询记录了慢查询关键字SlowQuery但应用日志里没打印 SQL 明细于是转头去看 MySQL 慢查询日志。查看慢查询日志尾部tail -n 200 /var/log/mysql/slow.log再用 mysqldumpslow 工具做汇总排序mysqldumpslow -t 5 /var/log/mysql/slow.logmysqldumpslow会把结构相似的 SQL 归类汇总-t 5取耗时最长的前 5 类。输出中排名第一的 SQL 没有走索引全表扫描行数超过了千万级。到这里问题链路完全打通某条 SQL 因为数据量增长失去了索引优化查询变慢数据库连接被长期占住连接池耗尽应用线程池也跟着阻塞最终表现为下单接口超时。5.3 这次排查里最关键的一个命令这次排查中真正起到决定性作用的是最后这条mysqldumpslow -t 5 /var/log/mysql/slow.log但更值得记住的是前一步提取上下文的 grepgrep -B 5 -A 20 DB_TIMEOUT /var/log/myapp/app.log因为它把应用层错误和数据库慢查询之间的因果关系串起来了。如果只看应用日志只知道超时了如果只查数据库慢查询不一定知道是哪个时间点开始恶化的。上下文窗口让你看到请求从哪里来、SQL 走向哪里这是日志排查里最核心的思维。很多人遇到这种问题会从头开始把日志一句一句读但正确的方式是先tail看最新的关键报错再grep统计异常频率再grep -A/-B提取上下文最后根据线索跳到下一个相关日志文件。这个过程不是靠某一个命令而是靠一条清晰的分析链路。6. 日志查看中的几个坑以及我的日常小技巧最后聊几个我在实际工作中踩过的坑和慢慢养成的小习惯。这些东西不会写在官方文档里但对线上排查体验的影响非常大。6.1 你 tail 不到内容日志文件其实已经被轮转有一次排查时我开着tail -f /var/log/myapp/app.log等了好几分钟日志一点动静都没有但业务方明确说刚才还在报错。直觉告诉我不是没有日志而是我看错了文件。随后执行ls -lht /var/log/myapp/发现确实有一个app.log.1更新于几分钟前原来 logrotate 刚把app.log重命名成了app.log.1然后新建了一个空的app.log。而我之前一直tail -f的是旧文件描述符指向的已改名文件新日志已经写到app.log.1里去了。从那以后我直接用tail -F替代tail -f-F会按照文件名重新打开文件哪怕文件被轮转重建也能自动跟上。这个习惯强烈建议你从现在开始养成。6.2 日志时间不连续先看时区跨服排查时最坑的问题是时区不一致。有的应用日志用 UTC有的用本地时间 CST如果两台服务器的日志时间戳一个带00:00一个不带直接按时间范围拼接日志会得到错乱的顺序。我现在的处理方式是每次跨服务器排查前先执行date确认系统时间再看日志第一行的时间格式如果日志本身只有本地时间没有时区就在心里做个换算。更稳妥的做法是在日志配置里强制使用 ISO 8601 格式比如2025-06-15T14:30:0008:00这种带时区偏移的格式后续不管是人工看还是写脚本分析都不会再被时区问题干扰。6.3 日志文件被进程占用但看不到内容还有一个隐蔽场景某个服务进程把日志文件打开了但后来这个文件被运维手工删除或轮转清理了。你在文件系统里看不到这个文件但进程依然持有旧文件的文件描述符日志依旧在往那个“已删除但未释放”的文件里写。这会导致磁盘空间看起来没少但实际空间被占用。排查方式lsof | grep deleted这条命令会列出所有被进程打开但已从文件系统删除的文件后面跟文件大小就能判断是不是大文件在占空间。如果你确认某个日志文件对这个服务已经没用了可以把对应进程重启一下释放句柄磁盘空间才会真正回收。这虽然不是严格意义上的“查看日志命令”但排查日志写满磁盘、日志丢失这类问题时经常要联合使用。6.4 一些让效率翻倍的小习惯第一给 grep 加颜色。Linux 默认的 grep 在终端里匹配到的关键词没有高亮如果日志行很长找起来费眼。建议在~/.bashrc里加一行alias grepgrep --colorauto之后 grep 结果里匹配到的部分会高亮显示扫日志的效率立刻提升。第二组合实时过滤。我经常用这条命令同时跟踪日志和过滤关键词tail -F /var/log/myapp/app.log | grep --line-buffered ERROR注意一定要加--line-buffered。因为 grep 在管道模式下默认会做块缓冲也就是输出不会立刻打印等到攒够一批数据才输出这样实时跟踪就名存实亡了。加上这个参数后 grep 每处理一行就输出一行配合tail -F才能做到真实时。第三固定自己常用的复杂命令。比如我有个 shell 函数专门用来同时跟踪多个日志文件并过滤关键词logtail() { tail -F $1 | grep --line-buffered -E $2 }用的时候执行logtail /var/log/myapp/app.log ERROR|WARN就很方便。第四日志文件巨大且单行超长时优先用less -S而不是直接 tail。less -S可以左右滚动查看超长行想看具体某个字段就水平移动日志的完整结构尽收眼底不会因为一行几十 K 而在终端里疯狂换行把屏幕刷得乱七八糟。第五grep 到二进制文件时终端会显示Binary file matches而不是输出匹配内容。这种情况通常发生在日志文件里混入了非文本字节可以加-a强制按文本处理grep -a ERROR /var/log/myapp/app.log我用过几次这条命令处理断电后产生乱码的日志文件效果立竿见影。这些命令和习惯单独看都不复杂但真正到线上故障时需要的是把它们熟练地串起来。日志文件没有噱头无非是找位置、看实时、过滤关键词、处理压缩文件、跨文件合并。把这一套流程练熟之后别人翻半天日志还没有头绪你几分钟就能定位到问题根因。