系列写到第九篇,这次我想聊聊日志分析和文本处理相关的那几个命令。前面几篇我们把文件、权限、进程、磁盘、网络都过了一遍,但很多朋友在实际干活时仍然觉得不够用,尤其是系统出了问题需要翻日志的时候,那种“每个命令都认识,组合起来却无从下手”的感觉特别明显。这篇我把日志查看、内容过滤、字段提取、统计汇总这一套完整串起来,看完你就能用纯命令行把几百MB甚至几个GB的日志文件理出清晰线索,找出报错根源,顺带还能统计出业务指标。适合刚接触服务器运维的新手,也适合写了些脚本但总在日志处理上卡壳的同学。
1. 日志文件在哪里:系统日志的常见路径与格式解析
1.1 别乱翻:先从系统的日志目录开始
很多同学一上来就find / -name "*.log",结果找出一堆跟自己无关的文件,浪费时间还容易被干扰。传统Linux系统日志集中在/var/log/目录,这是约定俗成的规矩,绝大部分软件都会把日志丢在这里。我最常用的是这几个:
/var/log/messages:系统整体运行信息,包括内核、服务启动、认证等信息。有些发行版改成了/var/log/syslog。/var/log/secure:认证与安全日志,SSH登录失败、sudo授权记录都会出现在这里。排查暴力破解基本就靠它。/var/log/cron:定时任务执行记录,任务没跑或者跑出错,先来这里看。/var/log/dmesg:内核环形缓冲区日志,硬件认不认、驱动报不报错都在这。
除了这些系统级日志,各种应用通常在自己安装目录或/var/log/下建子目录,比如Nginx写/var/log/nginx/access.log和error.log,MySQL写/var/log/mysql/error.log。建议先ls /var/log/看一下这个目录里都有什么,心里有数,别一上来就全局搜索。
1.2 日志格式与字段含义
日志为什么难看懂?因为没有统一标准。但绝大多数日志都是“时间 + 来源 + 事件描述”的结构。比如常见的一行:
Dec 5 10:23:45 web01 sshd[12345]: Failed password for root from 203.0.113.7 port 22 ssh2拆开看就是时间(Dec 5 10:23:45)、主机名(web01)、进程名和PID(sshd[12345])、具体消息(Failed password...)。这种格式叫syslog格式,理解它之后,用grep按关键词搜,用awk按位置取字段,思路就打通了。
Nginx的访问日志是另一套常见格式,默认叫combined格式:
127.0.0.1 - - [10/Dec/2025:10:23:45 +0800] "GET /api/v1/user HTTP/1.1" 200 512 "http://example.com/" "Mozilla/5.0 ..."这里用空格和引号做了分隔,IP是第一列,请求时间在方括号里,请求行是第六列,状态码是第九列。知道这些字段位置,写分析命令就有依据了。
1.3 不同服务的日志差异
每个服务都有自己的日志风格,没有万能钥匙,但有一套通用的勘察方法:先看配置文件里log_path或logging相关参数,确认日志写到哪;再直接打开日志文件看头几十行,了解格式。很多应用还支持日志等级配置,比如Nginx的error_log可以指定debug、info、warn、error等级别,调试时调到debug,排查完再调回来,否则生产环境会产生海量日志。
注意:别在业务高峰期随意改动日志等级,尤其是数据库和高并发网关,debug日志能把磁盘瞬间写满。我见过某团队把线上Nginx error_log调成debug,半小时磁盘报警。
2. 查看日志的基本姿势:tail、head、less与grep组合
2.1 实时跟踪日志:tail -f 与 tail -F
排查线上问题时,最常用的是实时看日志。tail -f /var/log/messages可以持续输出新追加的内容。这里有个经典坑:如果日志被logrotate轮转了,即原来的文件被改名或者重建,tail -f会一直盯着旧文件,导致新日志不显示。正确做法是用大写的tail -F,它会按文件名重新定位文件,文件被重建后自动跟随新文件。日常操作我建议直接写tail -F,能省很多不必要的事故。
如果要同时看多个日志,tail -F /var/log/nginx/access.log /var/log/nginx/error.log还能加-n 50先回放最近的50行再跟随。这个在调试接口时特别好用,一边发请求一边看错误日志和访问日志。
2.2 按行号与关键词定位:less + grep 的高效配合
日志文件动不动几千行,直接cat肯定不合适。less是最舒服的阅读工具,支持上下翻页、按行号跳转、搜索。打开一个日志:less /var/log/messages,然后按Shift+G跳到末尾,按g回到开头,按/输入关键词回车就可以向下搜索,按?向上搜索,按n继续下一个匹配项。
但很多时候我们不是漫无目的地看,而是带着关键词来排查。比如我想找报错:grep -i error /var/log/messages。-i忽略大小写,避免漏掉ERROR、Error这些变体。但这样输出可能也很多,更好的方式是先用grep error /var/log/messages | less,把过滤后的内容交给less翻看。又或者配合-A、-B看上下文:grep -B 2 -A 5 "OutOfMemory" /var/log/messages,这样可以看到错误发生的背景和前因后果,比单看一行有用得多。
2.3 小心坑:grep过滤后你可能会丢字段
很多人用grep直接过滤,然后发现拿到的内容不完整。比如Nginx日志里有一个请求体很大,日志可能被截断或者换行显示,grep单行匹配只能拿到断掉的部分。这时候可以考虑grep -P支持Perl正则,或者干脆结合awk通过行内特征匹配。还有一点:grep默认是“包含即匹配”,有时候你想找以某个词开头的行,就得多加几个条件。比如只看“Failed password”开头的行,用grep "^Failed password"反而会漏掉前面有时间戳的syslog格式。这种场景下,匹配“Failed password”出现在任意位置就好,不必执着于行首。
还有一个进阶技巧:如果日志文件很大,比如超过500MB,直接grep其实比较慢。可以先用split把文件按大小切块,或者用less打开后再用less内部的搜索,也能从末尾逐步加载。但最稳妥的办法还是下面要讲的sed按时间范围截取。
3. 文本处理三剑客在日志分析中的实战
3.1 sed:按时间范围截取日志
很多时候我们只关心某个时间段内的日志。比如今天上午10点到10点20分之间发生了什么,grep不太好写,因为时间字段在行首的格式可以用正则匹配,但写起来容易错。用sed按行号或正则范围来截取就灵活得多。
假设日志格式是Dec 5 10:23:45 ...,我想截取10点整到10点20分的日志,可以这样:
sed -n '/Dec 5 10:00:00/,/Dec 5 10:20:59/p' /var/log/messages但这里有个隐患:如果10点20分59秒这一行恰好不存在,范围会一直延伸到下一个匹配的10点20分59秒之前,实际会导致多截取。更稳的做法是,先grep找到起止行号,再用sed -n '100,200p'按行号截取。两个命令配合:
start=$(grep -n 'Dec 5 10:00:00' /var/log/messages | head -1 | cut -d: -f1) end=$(grep -n 'Dec 5 10:20:59' /var/log/messages | tail -1 | cut -d: -f1) sed -n "${start},${end}p" /var/log/messages这样能精确截取到已有日志的范围。虽然写起来麻烦,但避免了“范围穿透”的问题。
3.2 awk:按列提取和统计日志数据
awk是日志分析的核心工具,几乎每个做运维的都离不开它。awk默认以空白字符为分隔符,适合syslog格式。比如我想列出所有尝试登录的来源IP:
awk '/Failed password/ {print $(NF-3)}' /var/log/secure为什么是NF-3?上面那行Failed password for root from 203.0.113.7 port 22 ssh2,用空格切开后,from是倒数第4个字段,IP是倒数第3个,所以$(NF-3)正好取到。如果你不确定字段位置,可以先awk '{print NF, $0}' | head打印每一行的字段总数和内容,确认了再提取。
对于Nginx日志,字段分隔是空格,但请求行里的引号含空格,直接用awk '{print $9}'可以取状态码(第9列)。如果想取请求耗时,那需要确认日志配置中是否加了$request_time,通常它是最后一列。统计平均响应时间可以这样:
awk '{sum += $NF; count++} END {if (count > 0) printf "avg = %.3f\n", sum/count}' /var/log/nginx/access.log$NF表示最后一个字段,这一招可以统计任意数值列的平均值,非常实用。
3.3 综合实战:统计某接口的响应时间和错误率
这里我给大家一个可以直接改着用的完整示例。假设业务方反馈“最近接口变慢了”,我们需要从访问日志里看/api/user/list这个接口的平均耗时和成功率。日志格式是combined,最后一列是响应时间,第9列是状态码,第7列是请求行。我们可以一步步拆:
先确认字段:
tail -1 /var/log/nginx/access.log | awk '{print "IP:", $1, "STATUS:", $9, "REQ:", $7, "TIME:", $NF}'如果$7是整个/api/user/list HTTP/1.1这种带协议的内容,可以用$7匹配开头来过滤:
awk '$7 ~ /^\/api\/user\/list/ {print $NF}' /var/log/nginx/access.log > /tmp/time.txt然后把所有耗时求和、算平均、找最大值:
awk '{sum+=$1; count++; if($1>max) max=$1} END {printf "count=%d avg=%.3f max=%.3f\n", count, sum/count, max}' /tmp/time.txt统计错误率也很简单,找出状态码不等于200且不是3xx的请求占比:
awk '$7 ~ /^\/api\/user\/list/ {total++; if ($9 >= 400) bad++} END {printf "total=%d bad=%d rate=%.2f%%\n", total, bad, (total>0 ? bad/total*100 : 0)}' /var/log/nginx/access.log这段代码用到了awk里的三目运算符,写法和C语言类似,很实用。有了这两组数据,就能回答“到底慢在哪”“错误率是不是升高了”,而不是凭感觉猜。
4. 日志轮转与清理:别让磁盘被日志塞满
4.1 logrotate的基本配置
日志文件如果不清理,几个月就能把磁盘写爆。系统自带的logrotate是标准答案。它的配置文件在/etc/logrotate.conf,各个应用的独立配置在/etc/logrotate.d/下。我们拿Nginx举例,它的配置大致是:
/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 nginx adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript }daily:每天轮转一次。rotate 14:保留最近14份日志。compress:轮转后的旧日志用gzip压缩,节省空间。delaycompress:推迟一次压缩,避免刚轮转的文件立即压缩导致某些工具写文件出问题。create:轮转后新建同名日志文件,并指定属主权限。postrotate:在轮转后给Nginx发USR1信号,让Nginx重新打开日志文件句柄。这一步非常关键,否则Nginx还会继续往旧文件里写,轮转就白做了。
sharedscripts的意思是这些脚本只运行一次,而不是每个日志文件都运行一次。配置好后,可以用logrotate -d /etc/logrotate.conf做调试(dry-run),确认没问题再实际执行。
4.2 手动轮转与压缩策略
有时候日志文件已经很大了,等不到系统轮转,需要手动处理。比如messages已经1.2GB,直接mv /var/log/messages /var/log/messages.bak再systemctl restart rsyslog,但这期间可能丢失少量日志,而且如果其他进程还持有文件句柄,旧文件空间不会立即释放(需要杀掉相关进程才释放)。更稳妥的手动方式是用logrotate脚本:
logrotate -f /etc/logrotate.d/nginx-f强制执行,不管是否到时间。手动处理时要注意:轮转后旧日志是压缩状态,需要用zcat或zgrep查看。命令记忆也很简单:zcat对应cat,zgrep对应grep,zless对应less。
4.3 排查日志占满磁盘的紧急处理
当df -h发现某个分区100%时,第一反应不是删文件,而是先找大文件:
du -ah /var/log | sort -rh | head -20然后看看有没有哪个日志已经巨大。有时候是某个写日志的进程把文件删了但没释放句柄,用lsof | grep deleted可以查出来,然后重启那个进程或evict句柄。还有一个小技巧:不掉线地清空日志用truncate -s 0 /var/log/xxx.log,这样文件大小归零,而进程不用重启,句柄依然有效。相比直接rm,truncate更安全,能避免“空间没降下来”的怪问题。
注意:千万别在生产环境用
rm -f /var/log/messages这种操作,把文件删掉后,rsyslog还在写原句柄,你会看到ls没有文件,但磁盘空间一直居高不下,最后还得靠lsof找出来,白白折腾。
5. 常见问题与排查技巧实录
5.1 日志文件被删除但空间不释放
某次磁盘告警,查看大文件时发现/var/log/messages不见了,但df显示空间占用还是很高。原因就是rsyslog进程持有旧文件句柄。可以用这个命令查看:
lsof | grep deleted找到带deleted的进程和文件句柄后,确认是对应服务,然后通过重启服务释放。不需要重启整个系统。处理完后再用df -h验证。
5.2 tail -f 与管道搭配的意外中断
在脚本里用tail -f access.log | grep error想要实时过滤输出时,你会发现当你用Ctrl+C中止后,管道后的grep可能没有退出,留下一个孤儿进程。这是因为管道两端的进程收到了不同的信号。解决方式:在脚本里使用timeout或显式指定进程组。如果只是临时排查,建议直接tail -F file | grep --line-buffered error,加--line-buffered让grep逐行输出,避免缓冲导致看不到实时内容。也可以不用管道,改用less打开日志后按/error搜索,这样既能看到上下文,也不会产生多余进程。
5.3 中文乱码与编码问题
某些应用日志包含UTF-8中文,用less查看时如果系统的locale不对,会显示乱码。先检查环境变量:
echo $LANG通常需要LANG=en_US.UTF-8或zh_CN.UTF-8。如果日志本身是GBK编码,可以用iconv -f GBK -t UTF-8 日志文件转换后再看。但更奇怪的是,有的日志文件名带中文,在shell里显示成转义序列,可以用ls -b看原始字节。别太依赖编辑器,命令行工具配合好env设置基本能解决大多数乱码。
5.4 按关键字统计的经典组合
统计某个关键字出现次数,新手喜欢grep -c,但grep -c统计的是匹配行数。如果一行里出现多次,也只会计一次。想要统计全部次数,得这样:
grep -o 'error' /var/log/messages | wc -l-o只输出匹配的部分,每出现一次就输出一行,然后wc -l数总和。统计多个关键词的分布,可以写成循环:
for word in error warning timeout; do echo -n "$word: " grep -o "$word" /var/log/messages | wc -l done而如果想按IP统计访问次数,经典的awk统计方式:
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' /var/log/nginx/access.log | sort -rn | head -10这是awk数组用来做频率统计的典型场景,count[$1]++以IP为下标累加,最后遍历打印。学会这个组合,以后不管是统计访问来源还是统计报错模块,都能举一反三。
写在最后
日志分析这块,命令本身都不复杂,真正的门槛是“组合思维”——什么时候该用grep过滤,什么时候该用awk取列,什么时候用sed截范围,心里要有个谱。我个人习惯是:先tail -F看实时状态,再用grep + less定位关键词,最后用awk做统计量化。遇到跨时间段的排查,优先想sed,但务必用行号范围避免正则穿透。还有一个建议:日常使用中养成随手alias几个高频组合的习惯,比如把tail -F | grep error缩写成一个短命令,能省不少重复输入。踩过几次坑之后,你会慢慢发现日志不再是一堆杂乱文本,而是一个有迹可循的现场,拿着命令行就能层层拆开,找到藏在表象下的真实原因。