日志查看命令实战:从tail、grep到journalctl与docker logs排错技巧
简介这是一份聚焦Linux日志查看与分析方法的小型PDF文档适合运维、开发及测试人员在日常排障与日志分析时快速参考。文档以命令对为主线系统梳理tail/head、cat/tac、less/more、grep/sed、wc等常用日志命令覆盖实时监控、分页查看、正则搜索、时间区间过滤、关键字上下文定位与行数统计等高频场景并给出组合命令示例和常见排错思路。除了基础参数说明还结合实际场景演示如何用grep匹配错误关键字、用sed截取指定时间段日志、用wc统计关键字出现次数以及通过tail -f持续追踪日志变化能帮助读者快速定位问题、提升排查效率。压缩包共1个PDF文件大小仅57KB内容精炼便携可直接在终端旁查阅。目前已有3138人学习下载尤其适合需要快速上手或巩固日志分析技能的初学者和中级IT工程师。1. 不是“查看 log”而是“筛 log”先搞清楚日志文件在哪一层线上出问题第一反应是打开 /var/log 或应用目录找 log 文件。但“查看日志”这个动作本身很容易走偏有人 tail -f 盯了十分钟什么也没等到有人对着几百 MB 的 error.log 一页页翻。日志查看的本质不是读而是筛——先用时间窗口、关键字、进程号把海量行压到眼睛能处理的量级再顺着上下文把因果链拼出来。下文按本地文件、系统与容器、统计检索、排错技巧四层把查看 log 的常用方法串成一套可复用的命令模板。适合需要自己排查问题的后端、运维、测试工程师有五年经验的人也可以重点看第 3 章的参数边界和第 5 章的时区坑。2. 本地文件日志tail、less、grep 组合出最小可用命令先定义“本地文件日志”应用直接写的文本文件比如 Spring Boot 的 application.log、Nginx 的 access.log/error.log、Python 脚本里 logging 模块输出到文件的 app.log。这类日志以换行分隔查看手段就是命令行三件套再加上专门处理轮转文件的 zcat/zgrep。2.1 为什么不先打开文件先看文件和修改时间按我的经验新到一个系统第一件事不是打开文件而是确认它现在是多大、多久没更新# 查看文件大小确认日志文件是否还在增长 ls -lh /var/log/app/application.log # 打印修改时间和文件属性判断是否被轮转过 stat /var/log/app/application.logls 的 Size 字段决定后续手段超过 500MB 的文件直接 vim 或 cat 是不现实的cat 会把内容全部推进终端缓冲区既慢又把屏幕刷满vim 打开大文件时行号计算和交换文件都很吃力。stat 里面的 Modify 时间如果停在几天前说明日志可能被 logrotate 改名了或者应用把新日志写到别的位置。这时先 ls -lht /var/log/app/ 看目录里哪个文件最新再决定跟踪对象。2.2 tail 实时跟踪的三个参数细节排查进行中的故障实时跟随是标配# 实时跟踪日志文件-F 会在 logrotate 重建文件后自动切换 tail -F -n 50 /var/log/app/application.log-F 和 -f 的区别是常见的盲点-f 按文件 inode 跟随文件被 logrotate 改名或重建后tail 会继续读旧文件的尾部表现是“怎么没有新日志了”-F 每秒检查一次文件是否被替换发现新 inode 后自动切换所以配合轮转文件应该用大写 F。如果你在脚本里跟踪日志希望进程挂了就停止跟随可以加 --pid12345 参数指定 PID 退出时 tail 自动结束。故障复现之后需要回到某一段具体内容。我一般先 Ctrl-C 停掉 tail再用 grep 打行号# 输出匹配行的行号取最近 20 处异常的定位信息 grep -n NullPointerException /var/log/app/application.log | tail -20拿到行号后用 sed 精确取一段范围# 根据 grep 结果的行号截取 10020 到 10050 行的上下文 sed -n 10020,10050p /var/log/app/application.log先 grep -n 定位行号再 sed 按范围取行比直接用 grep -A -B 更可控当同类异常出现几百次时grep -A 5 -B 5 的输出会非常长全部扫一遍既费眼睛又抓不到最近一次。想看最新一批错误时先把行数限制住再过滤# 只取最后 100 行匹配 ERROR/WARN 并保留颜色输出 tail -100 /var/log/app/application.log | grep -E ERROR|WARN --coloralways这条管道适合启动阶段快速判断有没有致命错误。--coloralways 在管道里强制保留颜色码如果后续还要分页接 less -R 可以继续翻页不会丢失高亮。2.3 轮转与压缩日志先确认“当前写的是哪个文件”logrotate 把 application.log 切成 application.log.1、application.log.2.gz 之后最常遇到的困惑是“文件怎么不长了”。先按时间排序列目录# 按修改时间倒序找出当前活动的日志文件 ls -lht /var/log/app/ | head -10确认活动文件名之后查旧日志里有没有某个关键字直接对压缩文件操作# 按块解压匹配避免把整个 .gz 读进内存 zgrep -n ERROR /var/log/app/application.log.1.gzzgrep 比 zcat file | grep 更省内存大压缩文件下优势明显。如果文件本身不带轮转而应用还在不断写要先看是不是磁盘被占满# 查看磁盘剩余空间排除“写不进去”导致的日志停止增长 df -h /var/log/app关于 MyBatis 这类框架很多人习惯把 SQL 日志打成单独文件。查“某个 SQL 为什么慢”时先看 MyBatis 输出到 file 的慢日志里有没有预编译语句再结合第 4 章的耗时统计能直接判断是 SQL 本身慢还是应用层卡住。SQL 日志里大量输出 PreparedStatement 参数行数膨胀很快记得让 logback 按大小轮转否则排查日志问题的当天就会吃掉几个 G。下表是这一节命令的速查场景首选命令选择理由实时跟踪并应对轮转tail -F文件重建后自动跟随新 inode只查最近区间tail -n 100避免全文件扫描查压缩旧日志zgrep -n按块解压内存占用小精确取上下文sed -n a,bp行号区间可控3. 系统与容器日志journalctl、docker logs 的常用查看姿势systemd 管理的服务日志不在 /var/log 下的普通文本里而是由 journald 收进二进制日志库。容器默认把 stdout/stderr 抓进 json 文件。它们的共同点是“不是普通文本文件”直接 grep 不到需要专用工具。3.1 journalctl 的时间窗口与 unit 过滤排查 systemd 服务时第一件事是确认服务名拼写# 查看 unit 是否处于 active确认服务名 systemctl status nginx.service确认服务名后用 journalctl 限制 unit 和时间窗口# 只查指定服务过去 1 小时的日志 journalctl -u nginx.service --since -1 hour--since 和 --until 是 journalctl 最值得记的参数支持 today、yesterday、-30 min、ISO 时间串。拿到崩溃时间点后把窗口收窄到 5 分钟# 指定精确时间窗口配合 -u 避免全量扫描 journalctl -u nginx.service --since 2024-06-01 10:00:00 --until 2024-06-01 10:05:00journald 默认有可能只保留最近几天的日志时间窗口设到一个月前会发现输出为空。先用这两个命令确认保留量# 查看 journald 当前占用空间 journalctl --disk-usage # 需要立即清理时裁剪到 200MB journalctl --vacuum-size200Mjournalctl -o verbose 会输出每条日志的全部元数据字段比如 _PID、_COMM、_SYSTEMD_UNIT这些字段可以用在条件过滤里# 只查看某个进程 PID 的日志适合跟踪单次请求 journalctl _PID12345 -o shortjournalctl 的过滤字段和值之间用等号多个条件即 AND 关系。这套字段体系是二进制日志比文本文件好查的核心原因不需要写正则直接按结构化字段精确匹配。下面是常用参数速查journalctl 参数作用常用值-u按 unit 过滤服务日志nginx.service--since / --until设置时间窗口today、-1 hour、ISO 时间-o verbose输出结构化字段short、json、verbose_PID12345按进程号精确过滤任意 PID3.2 docker logs 的参数与日志落盘位置容器应用按 12-factor 实践把日志打到 stdoutdocker 负责收集# 显示最后 100 行并带时间戳持续跟随输出 docker logs --tail 100 --timestamps --follow my_app--tail 限定初始输出行数--timestamps 在每行前加时间戳--follow 等效于 tail -f。只看某时间段的日志# 限定时间窗口注意 --since 和 --until 按 UTC 解析 docker logs my_app --since 2024-06-01T10:00:00 --until 2024-06-01T10:05:00这里最容易踩的坑是时区docker logs 的时间戳参数按 UTC 解析而应用日志里的时间可能是 CST。如果应用在容器里设置了 timezone日志显示 UTC8而 --since 给的是本地时间查出来的窗口会偏移 8 小时。处理方式是在命令前用 date -u 确认当前 UTC 时间或者干脆把 --since 写成 UTC 的 ISO 字符串。docker 的日志文件默认存在 /var/lib/docker/containers/容器ID/容器ID-json.log内容是 JSON 行直接 grep 这个文件也有效# 在全部容器日志中搜异常关键字注意需要 root 权限 grep -E error|exception /var/lib/docker/containers/*/*-json.log | tail -20生产环境不能放任 json 文件无限增长给 daemon 配上轮转参数在 /etc/docker/daemon.json 中写入{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }改完 docker restart 或 reload 生效。max-size 50m、max-file 3 意味着最多保留 150MB 日志排错需要更长历史时再临时调大。提示docker logs 只收集 stdout/stderr。应用如果用 log4j 直接写文件docker logs 什么都看不到先把日志框架的输出目标改成 console再让 docker 收集。3.3 kubectl logs 的两个冷门参数K8s 里最常见的查看命令是 kubectl logs但两个参数经常被忽略。第一多容器 Pod 要指定容器名# 查看指定容器日志main 是容器名而不是 Pod 名 kubectl logs deployment/my-app -c main --tail50第二容器崩溃重启后要看上一个实例的日志# 查看容器上一次运行的日志CrashLoopBackOff 时必用 kubectl logs my-app-pod-xxxx --previous--previous 解决 CrashLoopBackOff 场景里“当前容器只有启动日志”的问题真正的 panic 堆栈在上一次运行里。如果连 --previous 都没有说明节点已经把容器清理了此时去节点上查 /var/log/pods/ / 下的持久化文件。多副本服务排错时kubectl logs 一次只能看一个 Pod同时间对比多副本日志可以用 kubectl logs -l appmy-app 或装一个 stern 工具按标签聚合输出并加 Pod 名前缀比逐个敲命令高效。4. 海量日志的检索与统计awk、sort、uniq 把日志变成指标日志查不出问题很多时候不是信息不够而是没有把行变成数字。几十万条访问日志肉眼看不出“10:00 到 10:01 的 500 变多”这种规律用管道把状态码、耗时、异常类型统计出来结论自己会浮现。4.1 grep 取行awk 取字段sortuniq 计数一条典型的 Nginx access log 长这样172.17.0.1 - - [01/Jun/2024:10:00:03 0800] GET /api/order/123 HTTP/1.1 200 532 curl/7.68$9 是状态码$7 是请求路径$NF 是最后一个字段。统计状态码分布# 限定时间窗口后按状态码计数sort 让相同状态码相邻 grep 2024-06-01 10:00 access.log | awk {print $9} | sort | uniq -c | sort -rn这段管道四段各干一件事grep 筛时间窗awk 抽字段sort 排序让相同值相邻uniq -c 合并计数最后 sort -rn 降序排列。关键点在于 uniq 只合并相邻的相同行没有 sort 直接 uniq 会统计出同一状态码的多行记录这是新手最容易漏的地方。统计某个接口的 P95 耗时不引入数据库也能算# 先按耗时升序排列再用 awk 取排序后 95% 位置的值 awk $9200 $7 ~ /^\/api\/order/ {print $NF} access.log | sort -n | awk {a[NR]$1} END {print a[int(NR*0.95)]}这里的 $NF 要求自定义 log_format 把 request_time 放在最后。第二条 awk 把排序后的值放进数组 aNR 是总行数int(NR*0.95) 是 95% 位置。没有数据库纯命令行的好处是快文件 500MB 以内基本秒级出结果。另一个有用场景是看异常类型分布# 去掉时间戳前缀取异常类名并统计 Top 20 grep -E ERROR|Exception application.log | sed -E s/^[0-9-] [0-9:.] // | awk {print $1} | sort | uniq -c | sort -rn | head -20sed 把“2024-06-01 10:00:03,123”这种时间前缀删掉剩下的行首就是异常类名。统计结果前几名通常指向根因而不只是最近一次 panic。如果某个异常计数很高但偶尔才打一次堆栈可以配合按分钟汇总# 按分钟聚合异常次数看是否集中在某个时段 grep TimeoutException application.log | awk {print $1, substr($2,1,5)} | uniq -cawk 的 substr 截取时间的 mm:ss 前 5 个字符输出从“日期 分钟”维度聚合。这个结果可以画出异常随时间的变化如果峰值出现在发布之后问题就基本定位了。4.2 慢查询日志的 Top N 与模板归一化MySQL 慢日志、接口耗时日志是两种最常见的“性能日志”。看 MySQL 慢日志最慢的 10 条# 提取 Query_time 数值并降序head 取最慢的前 10 条 grep Query_time slow.log | sed s/.*Query_time: \(.*\) Lock_time.*/\1/ | sort -rn | head -10sed 的捕获组提取 Query_time 和 Lock_time 之间的数字保留两位小数。但最慢的 10 条往往是一条带具体参数的 SQL看不出整体分布。把具体参数替换成模板再聚合# 用 getline 读 SQL 行数字替换成 ? 得到模板按平均耗时排序 awk /^# Query_time/ {t$3; getline; gsub(/[0-9]/,?,$0); sql$0; sum[sql]t; cnt[sql]} \ END {for (s in sum) print cnt[s], sum[s]/cnt[s], s} slow.log \ | sort -k2 -rn | head -20这里的 awk 逻辑拆开看匹配到 Query_time 行时取第 3 列耗时getline 读下一行 SQLgsub 把所有数字替换成 ?比如 WHERE id? 这种模板同一模板的耗时累加到 sum 里条数累加到 cnt。最后的输出是“执行次数、平均耗时、SQL 模板”三列sort -k2 -rn 按第二列平均耗时降序。这个命令比 mysqldumpslow 灵活的地方是模板化的规则可以自己控制想把日期字段也归一化就把 gsub 的正则改成匹配日期时间的表达式。第 4 章的管道可以抽象成一张表方便按需组合管道段作用常用选项grep先把行数压下来-E、-v、-csed删除或提取行内片段-E、-n、-eawk按列抽取与统计{print $N}、substr、getlinesort排序为 uniq 做准备-n、-k2uniq相邻合并并计数-chead/tail取 Top N 或最近 N-205. 排错场景里的日志查看技巧上下文、时间线与时区最后是几个实际排错时高频使用的技巧单独拧出来不容易从文档里查到但它们决定了你能否在十分钟内定位问题而不是把时间耗在“看日志”这个动作本身。5.1 用 less 的关键字跳转替代反复 grepgrep 能找到行但会打散文件的原始顺序。排错需要“现场感”时用 less 打开大文件按 / 输入关键字回车n 跳到下一处匹配Shift-N 回上一处# -N 显示行号-R 保留日志里的颜色转义 less -N -R /var/log/app/application.log看到第一条 Exception 后连续按 n 往下翻同时留意相邻行的时间戳和调用链。多行堆栈在 less 里不会被 grep 拆成碎片上下文始终连续。这也是为什么我处理几百 MB 日志时首选 less 而不是 vimvim 加载慢less 按需读页翻到哪读到哪。5.2 从 error 行往前翻 200 行找到“原因行”错误行往往不是第一现场只是最终结果。在 less 里定位到 ERROR 后按 200k 向上翻 200 行这个数值约等于一个请求从进入到出错的正常跨度。如果翻到顶部还没找到可疑输入说明日志级别太高或者请求上下文不足需要在应用入口过滤器里生成 requestId放入 MDC让日志 pattern 输出它。排错时按 requestId 聚合同一个请求的所有日志# 按 requestId 聚合同一次请求的日志还原时间线 grep req-20240601-001 application.log | awk {print $1, $2, $0}有了 requestId多线程交错写的日志也能按请求还原顺序不用再靠“往上翻 200 行”碰运气。5.3 时间戳与时区偏差的快速换算日志时间比实际时间快 8 小时通常不是应用 bug是时区配置不一致。先建立基准线# 同时输出 UTC 和本地时间直接看出偏移 date -u date如果本地是 CSTUTC8date -u 输出 02:00date 输出 10:00偏移 8 小时。再看日志里某一行的原始时间字符串用 date 把它转成 epoch 秒# 把日志时间字符串转成秒级时间戳方便跨文件对比 date -d 2024-06-01 10:00:00 %s用同样的方式把宿主机当前时间也转成 epoch两个数值一减就能确认日志里的时间是 UTC 还是本地时区。容器里 Java 应用如果没有设置 user.timezoneSimpleDateFormat 默认按 UTC 格式化而宿主机显示 CST这就是“日志快了 8 小时”的来源。修复方式是在启动参数加 -Duser.timezoneAsia/Shanghai或者在 logback pattern 里用 %date{yyyy-MM-dd HH:mm:ss, Asia/Shanghai}。排查时先确认时区不要在脑子里加 8 小时。最后一个小习惯当你怀疑日志查看命令的输出有问题先跑一条最简单的命令确认输入源——docker logs 之前先 docker ps 确认容器名journalctl 之前先 systemctl status 确认 unit。日志在前端显示得再漂亮前置条件错了后面查到的每一行都是噪音。本文还有配套的精品资源点击获取