1. 项目概述:为什么“常见服务器日志分析”不是运维的选修课,而是生存基本功
你有没有遇到过这样的场景:凌晨两点,监控告警突然炸屏——CPU飙到98%,HTTP 503错误每秒上百条,但翻遍进程列表和资源占用,就是找不到“真凶”;又或者业务方急吼吼地问:“用户反馈下单失败,是不是我们系统崩了?”你打开Nginx访问日志,满屏200、304、499,却在第8742行发现一行不起眼的400 Bad Request,后面跟着一串被截断的JSON参数——而正是这个参数里少了一个必填字段,导致下游支付网关直接拒收,整条链路卡死。这不是戏剧桥段,是某次大促前夜我亲身经历的真实现场。
“常见服务器日志分析”这八个字,表面看平平无奇,像教科书目录里最不起眼的一节,但实际它是一道隐形分水岭:一边是靠“重启大法”和“等告警自动恢复”的被动响应者,另一边是能从一行tail -f /var/log/nginx/access.log | grep '499'中嗅出连接超时根源、从/var/log/auth.log里连续三分钟的Failed password暴露出暴力破解节奏、甚至从/var/log/syslog中kernel: Out of memory: Kill process的报错时间戳,反向推演出内存泄漏模块上线的确切版本号的人。它不依赖昂贵的APM工具,不绑定特定云厂商,只靠Linux基础命令、对日志格式的肌肉记忆,以及对服务间调用逻辑的直觉判断。
我带过的十几位刚转岗的运维新人,几乎都卡在这个环节:他们能熟练部署K8s集群,能写YAML编排服务,但当线上出现“偶发性慢请求”时,第一反应是查Prometheus图表,而不是grep -A 5 -B 5 'slow_query' /var/log/mysql/slow.log。结果呢?图表显示平均RT正常,但日志里藏着大量Query_time: 12.845s的慢SQL——因为慢查询日志默认只记录超过10秒的语句,而业务方抱怨的是“偶尔卡顿3秒”,这3秒根本进不了慢日志,却真实存在于access.log的$request_time字段里。这种认知偏差,本质是对日志作为“系统原始神经信号”的价值理解不足。日志不是事后复盘的备忘录,它是系统运行时最诚实、最细粒度的实时直播流。
这篇文章要讲的,就是如何把这份“直播流”真正看懂、用活。不堆砌高大上的ELK架构图,不空谈SRE理念,就聚焦在你每天SSH登录后最先敲的那几条命令上:tail、grep、awk、sort、uniq怎么组合才能30秒定位接口抖动元凶;Nginx、Apache、Tomcat、MySQL、Redis这五大件的日志字段,哪些是必盯的“生命体征”,哪些是干扰项;当/var/log目录下塞满几十GB的.gz压缩包时,如何不用解压就能快速检索关键错误。所有内容,都来自我在金融、电商、SaaS三类业务线累计处理的2700+次线上故障排查实战,其中73%的根因定位,核心步骤不超过5条Shell命令。接下来,我们就从日志分析的底层逻辑开始拆解。
2. 日志分析的整体设计与思路拆解:为什么“先过滤再统计”比“全量导入再查”更可靠
很多人一想到日志分析,脑子里立刻跳出Elasticsearch+Logstash+Kibana的黄金组合,或是某云厂商的“智能日志分析服务”。这没错,但对于日常高频、低延迟的故障排查,这套方案反而成了负担。我经历过最典型的反面案例:某次支付回调超时,业务方要求10分钟内给出结论。团队同学立刻启动日志平台,配置索引、设置时间范围、编写查询DSL……等界面终于加载出结果,已经过去8分钟,而问题在第3分钟就已自行恢复——你分析的,是历史快照,不是实时脉搏。更致命的是,日志平台本身也是服务,当它所在节点CPU打满时,你的查询请求会排队、超时、返回空结果,形成“用诊断工具去诊断诊断工具”的荒诞闭环。
所以我的日志分析设计哲学,第一条就是:日志分析的主战场永远在生产机本地,而非远端平台。这不是技术保守,而是基于三个硬约束:
- 时效性约束:网络IO、序列化、索引构建、查询解析,每一环都增加毫秒级延迟。而
zcat access.log.1.gz | awk '$9 > 5 {print $1,$7,$9}' | head -20这条命令,从敲下回车到输出结果,实测在万兆网卡+SSD的机器上稳定在1.2秒内; - 可靠性约束:日志平台挂了,你还有
/var/log目录;但/var/log目录损坏了,日志平台连数据源都没了。本地分析是最后的兜底能力; - 权限约束:很多金融、政务类客户环境,日志禁止外传,连内网日志平台都需单独审批。此时
less +G /var/log/nginx/error.log就是唯一合法入口。
第二条设计原则是:拒绝“全量扫描”,拥抱“条件前置”。新手常犯的错误是grep 'ERROR' /var/log/app.log,看似简单,实则埋雷。某次排查一个Java应用OOM,我看到同事执行这条命令后,终端卡住近两分钟才吐出结果——因为app.log有12GB,而ERROR是通用词,日志里org.springframework.web.servlet.DispatcherServlet ERROR这类框架日志占了90%。正确做法是先缩小范围:grep -n 'java.lang.OutOfMemoryError' /var/log/app.log,精准定位到报错行号,再用sed -n '12345,12360p' /var/log/app.log提取上下文。这背后是Unix哲学的精髓:每个工具做一件事,并做好。grep负责筛选,sed负责裁剪,awk负责计算,它们通过管道(|)串联,而非让一个工具承担全部逻辑。
第三条是字段意识优先于格式记忆。很多人背Nginx日志格式'$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time',却忽略$request_time和$upstream_response_time的本质区别:前者是Nginx接收完整请求到发送完响应头的时间,后者是Nginx向后端(如Tomcat)发起请求到收到响应头的时间。当$request_time高但$upstream_response_time低,说明瓶颈在Nginx自身(如SSL握手、gzip压缩);反之则问题在后端。这种差异,比记住整个格式重要十倍。
最后一点,也是最容易被忽视的:日志分析必须与业务链路强绑定。单纯看500 Internal Server Error没意义,要结合业务场景。比如电商系统里,500出现在/api/order/create接口,大概率是库存扣减失败;而出现在/api/user/profile,可能是用户中心服务不可用。我习惯在分析前,先画一张极简的调用链草图:用户请求→Nginx→API网关→订单服务→库存服务→数据库。然后带着这张图去看日志,每一行错误都会自动映射到某个环节。这种“带着地图找路”的方式,比盲目扫日志效率高出数倍。
提示:不要试图用一套命令解决所有问题。我电脑里存着十几个按场景命名的Shell脚本,如
find_slow_nginx.sh(专查Nginx慢请求)、check_auth_bruteforce.sh(检测SSH暴力破解)、mysql_long_trx.sh(抓取MySQL长事务)。它们的核心逻辑都是“条件前置+字段聚焦”,而非通用搜索。
3. 核心细节解析与实操要点:五大件日志字段解剖与关键指标盯控
日志分析的成败,70%取决于你是否真正理解每种日志里那些看似枯燥的字段含义。它们不是随机排列的字符,而是系统运行状态的编码。下面我以Nginx、Apache、Tomcat、MySQL、Redis这五类最常见服务为例,逐个拆解其日志中必须盯紧的“生命体征”字段,并给出实操中极易踩坑的细节。
3.1 Nginx日志:$request_time与$upstream_response_time的博弈
Nginx访问日志(access.log)是Web层的“交通摄像头”,记录每一次HTTP请求的完整轨迹。标准格式中,最关键的三个时间字段是:
$request_time:Nginx处理整个请求的耗时(单位:秒,精度为毫秒),从接收到第一个字节到发送完最后一个字节;$upstream_response_time:Nginx与上游服务器(如后端Java应用)交互的耗时,格式为0.123, 0.456(逗号分隔,表示多次重试的各次耗时);$upstream_connect_time:Nginx与上游建立TCP连接的耗时(单位:秒)。
为什么盯这两个时间?因为它们能直接定位性能瓶颈在Nginx自身还是后端服务。举个真实案例:某次用户投诉“首页加载慢”,$request_time平均值达3.2秒,但$upstream_response_time只有0.08秒。我立刻执行:
# 抓取request_time > 2秒且upstream_response_time < 0.1秒的请求 awk '$9 > 2 && $12 < 0.1 {print $1,$7,$9,$12}' /var/log/nginx/access.log | head -10结果发现所有慢请求都集中在/static/css/app.css这类静态资源,而$upstream_response_time为空(因为静态文件由Nginx直接返回,不走upstream)。进一步检查,发现Nginx开启了gzip on,但gzip_types未包含text/css,导致CSS文件未压缩,体积达2.1MB。关闭gzip或添加类型后,$request_time降至0.15秒。
实操避坑点:
$upstream_response_time字段在Nginx配置中需显式开启:log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time $upstream_response_time';若未配置,该字段为空;$request_time是浮点数,awk处理时需用$9 > 2.0而非$9 > 2,否则可能因类型转换出错;- 当Nginx启用
proxy_next_upstream重试时,$upstream_response_time会记录所有重试耗时,用awk '{split($12,a,","); print a[1]}'可取第一次重试时间。
3.2 Apache日志:%D与%T的精度陷阱
Apache的Common Log Format(CLF)中,时间字段有两个:%t(请求时间戳)和%D(请求处理耗时,单位:微秒)。但很多管理员不知道,%D存在严重精度陷阱:它只计算Apache子进程处理请求的时间,不包括网络传输、DNS解析、SSL握手等前端耗时。某次排查HTTPS慢请求,%D显示平均120ms,但用户实际感知超2秒。我改用%T(处理耗时,单位:秒,精度为毫秒)并配合%{SSL_PROTOCOL}x字段:
# 查看SSL协议版本与耗时关系 awk '{if($13 ~ /TLSv1.2/) t12+=$11; else if($13 ~ /TLSv1.3/) t13+=$11} END {print "TLS1.2 avg:",t12/NR,"TLS1.3 avg:",t13/NR}' /var/log/apache2/access.log结果发现TLSv1.2平均耗时1.8秒,而TLSv1.3仅0.23秒——根源是旧版OpenSSL的RSA密钥交换性能差。
实操避坑点:
%D和%T不能同时使用,需在LogFormat中二选一;- Apache 2.4+才支持
%{SSL_PROTOCOL}x,低版本需升级; %D数值极大(微秒),awk计算时注意除以1000000转换为秒。
3.3 Tomcat日志:catalina.out里的“沉默杀手”
Tomcat的catalina.out不是结构化日志,而是标准输出(stdout/stderr)的混合流,包含启动日志、GC日志、异常堆栈。它的危险在于:大量INFO级别日志会淹没真正的ERROR。某次线上服务假死,catalina.out里每秒刷出上千行INFO [http-nio-8080-exec-XX] o.a.c.c.C.[.[.[/].[] - Initializing Spring DispatcherServlet,而真正的java.lang.OutOfMemoryError: Java heap space被埋在第12万行。
高效定位法:
- 用
grep -n 'Exception\|Error\|OutOfMemory' catalina.out精准定位错误行号; - 结合
jstack和jmap:发现OOM后,立即执行jstack -l <pid> > jstack.log获取线程快照,jmap -histo:live <pid> | head -20查看对象分布,往往能发现char[]或byte[]异常增长; - 关键配置:在
logging.properties中将org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/].level = WARNING,降低INFO日志量。
实操避坑点:
catalina.out会不断追加,需用logrotate定期切割,否则单文件过大导致grep极慢;jstack需用与Java进程同用户的权限执行,否则可能无权限读取;jmap -histo会触发Full GC,生产环境慎用,建议先jmap -dump:format=b,file=heap.hprof <pid>再离线分析。
3.4 MySQL日志:慢查询日志的“时间阈值”玄机
MySQL慢查询日志(slow_query_log)的long_query_time参数,常被误解为“只要查询超X秒就记录”。但真相是:它只统计SQL执行时间,不包括锁等待、网络传输、客户端处理时间。某次业务方反馈“订单查询慢”,慢日志里却找不到对应SQL。我执行:
-- 查看当前会话的锁等待情况 SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; -- 查看锁冲突详情 SELECT * FROM information_schema.INNODB_LOCK_WAITS;发现大量事务在等待orders表的行锁,根源是另一个未提交的UPDATE orders SET status='paid' WHERE user_id=123事务。
实操避坑点:
long_query_time最小值为0,设为0可记录所有查询(但日志量爆炸,仅限临时诊断);- 启用
log_queries_not_using_indexes需谨慎,索引失效的查询可能海量,建议搭配min_examined_row_limit限制; - 慢日志默认不记录
SET、SHOW等管理命令,需log_slow_admin_statements=ON开启。
3.5 Redis日志:MONITOR命令的“双刃剑”
Redis的redis-server.log通常很安静,但当出现OOM command not allowed when used memory > 'maxmemory'时,说明内存已爆。此时MONITOR命令能实时捕获所有操作,但它是“双刃剑”:
- 正面:
redis-cli MONITOR | grep -E 'set|get|del'可快速定位高频Key; - 负面:
MONITOR会显著降低Redis性能(实测QPS下降40%),且日志无时间戳,需配合date命令:
# 安全的MONITOR用法(限制100条后退出) (timeout 30 redis-cli MONITOR | head -100) 2>/dev/null | while read line; do echo "$(date '+%H:%M:%S') $line"; done实操避坑点:
MONITOR在生产环境禁用,仅限紧急排查;- 内存问题优先查
INFO memory和MEMORY USAGE key_name; redis-cli --bigkeys可扫描大Key,但会阻塞Redis,建议在从库执行。
4. 实操过程与核心环节实现:从一条告警到根因定位的完整链路
现在,我们把前面所有知识点串起来,还原一次真实的故障排查全过程。场景设定:某电商平台在晚高峰(19:00-20:00)期间,订单创建接口/api/order/create成功率从99.98%骤降至92.3%,监控显示HTTP 500错误激增。以下是我在15分钟内完成的标准化操作链路,每一步都有明确目的和命令解释。
4.1 第一步:锁定时间窗口与错误模式(耗时2分钟)
目标:确认问题发生的具体时间段,并初步判断错误类型。
# 1. 确认Nginx访问日志中500错误的时间分布(假设日志按小时切割) zcat /var/log/nginx/access.log.20231015-19.gz | awk '$9==500 {print $4}' | sort | uniq -c | sort -nr | head -5 # 输出示例: 1241 [15/Oct/2023:19:23:45 --> 高峰集中在19:23-19:24 # 2. 抽样查看500错误的请求URL和User-Agent,排除爬虫干扰 zcat /var/log/nginx/access.log.20231015-19.gz | awk '$9==500 {print $7,$12}' | head -10 # 发现大量 /api/order/create?source=app,且User-Agent含"Android/12",确认是真实用户请求为什么这么做?直接跳过“查监控图表”的环节,因为日志自带时间戳,比监控系统更精确(监控采样间隔通常为15秒或1分钟)。抽样User-Agent是为了快速排除“爬虫刷单导致500”的可能性——如果是爬虫,User-Agent会是python-requests或curl,而非真实APP标识。
4.2 第二步:穿透Nginx,定位后端服务(耗时3分钟)
目标:确认500错误是Nginx自身问题,还是上游服务(如订单服务)返回的。
# 1. 提取该时间段内500请求的upstream_response_time(需Nginx已配置此字段) zcat /var/log/nginx/access.log.20231015-19.gz | awk -v start="19:23:00" -v end="19:24:00" \ '$4 ~ /\[.*15\/Oct\/2023:'start'|'end'/ && $9==500 {print $12}' | awk -F',' '{print $1}' | sort -n | tail -5 # 输出示例: 0.001 0.002 0.003 0.004 0.005 --> 所有upstream耗时均小于10ms,说明Nginx瞬间得到后端响应,问题不在网络 # 2. 检查Nginx错误日志,确认是否自身报错 zcat /var/log/nginx/error.log.20231015-19.gz | grep -i "upstream.*timeout\|500.*connect" | head -3 # 无输出,排除upstream超时或连接失败关键洞察:upstream_response_time极短(毫秒级),且error.log无相关报错,基本可断定:Nginx只是“信使”,500是后端服务主动返回的状态码。下一步必须切到订单服务日志。
4.3 第三步:切入订单服务日志,抓取异常堆栈(耗时4分钟)
目标:在订单服务日志中找到与Nginx 500时间戳匹配的Java异常。
# 1. 订单服务日志通常按天切割,且含时间戳(如2023-10-15 19:23:45.123) # 先用grep快速定位该分钟内的ERROR行 grep "2023-10-15 19:23:" /var/log/order-service/app.log | grep -i "error\|exception" | head -5 # 输出示例: 2023-10-15 19:23:45.789 ERROR [http-nio-8080-exec-23] c.e.o.c.OrderController : Create order failed # java.lang.RuntimeException: Inventory service timeout # 2. 提取完整异常堆栈(-A 20表示后续20行) grep -A 20 "Inventory service timeout" /var/log/order-service/app.log | head -30 # 发现关键线索:Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://inventory-service/api/inventory/deduct": Read timed out为什么用-A 20?Java异常堆栈通常跨多行,-A参数确保捕获完整的Caused by链。这里清晰指向“库存服务超时”,问题域已从订单服务缩小到库存服务。
4.4 第四步:深挖库存服务,定位根因(耗时5分钟)
目标:确认库存服务为何超时,是自身问题还是依赖下游。
# 1. 切换到库存服务日志,搜索同一时间戳的ERROR grep "2023-10-15 19:23:45" /var/log/inventory-service/app.log | grep -i "error\|timeout" # 输出: 2023-10-15 19:23:45.890 ERROR [http-nio-8080-exec-15] c.e.i.c.InventoryController : Deduct inventory failed # com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure # 2. 检查MySQL错误日志(库存服务依赖数据库) tail -100 /var/log/mysql/error.log | grep -i "timeout\|kill\|oom" # 输出: 2023-10-15T19:23:46.123456Z 123456 [Warning] Aborted connection 123456 to db: 'inventory_db' user: 'app_user' host: '10.0.1.100' (Got an error reading communication packets) # 3. 验证MySQL连接池状态(登录MySQL执行) mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Threads_created';" # 输出: Threads_connected: 256(已达max_connections=256上限) Threads_created: 1200(1小时内创建了1200个新连接,说明连接泄漏)最终根因:库存服务存在数据库连接泄漏,导致连接池耗尽,新请求无法获取连接,进而超时,最终引发订单服务500。修复方案是检查库存服务代码中Connection.close()是否被正确调用,或升级HikariCP连接池配置。
4.5 第五步:验证与收尾(耗时1分钟)
目标:确认修复后指标恢复正常。
# 1. 重启库存服务(假设已修复连接泄漏) systemctl restart inventory-service # 2. 实时监控Nginx 500错误率(10秒刷新一次) watch -n 10 'zcat /var/log/nginx/access.log.* | awk '\''$9==500 {c++} END {print "500 Rate: " (c/NR*100)"%"}'\' # 30秒后,500 Rate从92%降至0.01%,确认修复成功经验总结:这套链路之所以高效,在于每一步都基于上一步的结论设计下一个问题,形成“问题→证据→新问题”的闭环。它不依赖任何外部工具,只靠Linux基础命令和对日志字段的深刻理解,是每个一线运维必须刻进DNA的操作范式。
5. 常见问题与排查技巧实录:那些文档里不会写的“血泪教训”
在数百次日志分析实战中,我踩过不少坑,也总结出一些文档里绝不会写、但能让你少走半年弯路的独家技巧。这些不是理论,而是真金白银买来的教训。
5.1 “grep不到”的三大幻觉及破解法
幻觉一:日志明明有,grep就是不显示
原因:日志文件编码非UTF-8(如GBK),或含不可见控制字符(如\r\n混用)。
破解法:
# 先用file命令看编码 file -i /var/log/app.log # 若为iso-8859-1,用iconv转换后再grep iconv -f iso-8859-1 -t utf-8 /var/log/app.log | grep "ERROR" # 检查控制字符 cat -A /var/log/app.log | head -5 # 显示^M、$等符号 # 若有^M(Windows换行),用dos2unix处理 dos2unix /var/log/app.log幻觉二:日志在,但时间对不上
原因:服务器时区不一致。Nginx日志用Asia/Shanghai,Java应用用UTC,grep "2023-10-15 19:23"自然找不到。
破解法:
# 统一用UTC时间查(Java日志常用) grep "$(date -d '2023-10-15 19:23 CST' -u '+%Y-%m-%d %H:%M')" /var/log/app.log # 或直接查时间戳(更可靠) grep "1697398980" /var/log/app.log # 将19:23:00转为Unix时间戳幻觉三:日志被轮转,grep提示“没有那个文件”
原因:logrotate已将app.log重命名为app.log.1,原文件被清空。
破解法:
# 用zcat通配符查所有压缩日志 zcat /var/log/app.log.*.gz | grep "ERROR" # 或用find查找最近修改的文件 find /var/log -name "app.log*" -mtime -1 -exec zcat {} \; | grep "ERROR"5.2 “日志太大”的终极瘦身术
当/var/log塞满100GB日志,grep卡死时,别急着删:
- 技巧1:用
awk替代grep做条件过滤grep "500" access.log需扫描全文件,而awk '$9==500' access.log在解析字段时就过滤,速度提升3倍; - 技巧2:用
parallel并行处理# 将大文件分割为100MB块,并行grep split -b 100M access.log access_part_ parallel -j 4 grep "500" {} ::: access_part_* - 技巧3:用
ripgrep(rg)替代greprg是grep的超高速替代品,对UTF-8日志优化极佳:rg "java.lang.OutOfMemoryError" catalina.out,实测比grep快8倍。
5.3 “误判根因”的经典陷阱
陷阱一:把现象当原因
看到OutOfMemoryError就以为是内存不够,重启JVM。实则可能是String.intern()滥用,导致字符串常量池溢出。
避坑法:
# 先查JVM内存分布 jstat -gc <pid> # 看Eden、Survivor、Old区使用率 # 若Old区持续增长,才是真内存泄漏;若Eden区频繁GC,可能是短生命周期对象过多陷阱二:忽略日志级别误导WARN级别的日志常被忽略,但WARN [org.springframework.web.servlet.DispatcherServlet] - Failed to complete request可能预示着@Transactional失效,导致数据不一致。
避坑法:
# 将WARN视为潜在ERROR,统一监控 awk '/WARN/ && /Failed|Error|Exception/ {print}' /var/log/app.log陷阱三:过度依赖日志,忽视系统指标
日志说“连接超时”,但netstat -an | grep :3306 | wc -l发现ESTABLISHED连接仅20个,远低于MySQL的max_connections=256。此时应查ss -s看socket内存是否耗尽,而非继续翻日志。
5.4 故障复盘清单:每次分析后的必做动作
我坚持在每次故障处理后,花5分钟填写这张极简清单,它让我在三年内将同类问题复发率降低了87%:
| 项目 | 内容 | 示例 |
|---|---|---|
| 时间锚点 | 问题开始/结束的精确时间(含时区) | 2023-10-15 19:23:05 CST - 19:24:30 CST |
| 日志证据链 | 关键日志行及命令 | zcat access.log.20231015-19.gz | awk '\$9==500' | head -3→...500 /api/order/create... |
| 根因一句话 | 用最直白的话描述 | 库存服务数据库连接泄漏,连接池耗尽 |
| 修复动作 | 具体执行了什么 | 重启库存服务,发布修复连接关闭的补丁v2.1.3 |
| 预防措施 | 如何避免再次发生 | 在CI流程中加入连接池监控告警,阈值>200连接持续5分钟 |
这张表不存档,不入库,就记在个人笔记里。但它让“故障分析”从救火行为,变成了可积累、可复用的经验资产。
6. 工具链精简指南:不装全家桶,只留最锋利的三把刀
市面上日志分析工具琳琅满目,但我的经验是:工具越少,越专注问题本身。我日常只用三款工具,它们覆盖了95%的场景,且全部开源、免安装、零配置。
6.1lnav:日志的“瑞士军刀”
lnav不是另一个less,它是专为日志设计的交互式查看器。安装只需apt install lnav(Ubuntu)或brew install lnav(Mac),无需任何配置。
- 核心优势:自动识别Nginx、Apache、MySQL等50+种日志格式,高亮时间戳、状态码、IP地址;
- 神技1:时间轴导航
按g跳转到最早,G跳转到最后,t输入时间(如19:23:45)直接定位; - 神技2:结构化查询
按:进入命令模式,输入filter-status 500,瞬间过滤所有500错误,再按q退出过滤; - 神技3:关联分析
在Nginx日志中选中一行500,按Ctrl+L,lnav会自动在下方窗口打开同一时间戳的catalina.out日志,实现跨服务关联。
提示:
lnav的/搜索支持正则,/java\.lang\.OutOfMemoryError比grep更准,因为它只在日志主体字段搜索,忽略时间戳和IP等干扰项。
6.2gron:JSON日志的“透视镜”
现代应用(如Spring Boot)大量输出JSON日志,{"level":"ERROR","service":"order","msg":"Create failed","traceId":"abc123"}。传统grep面对嵌套JSON束手无策。gron将JSON转为可grep的平面文本:
# 将JSON日志转为gron格式 gron /var/log/order-service/json.log | grep "level.ERROR" # 输出: json.level = "ERROR"; # json.service = "order"; # json.msg = "Create failed"; # 进阶:提取traceId并关联其他日志 gron /var/log/order-service/json.log | awk '/traceId/ {print $3}' | sed 's/"//g' | head -1 | xargs -I{} grep {}gron的妙处在于,它让JSON日志回归Unix哲学:每个工具做一件事。gron负责解构