面对完全陌生的线上应用,我靠这套"找日志"方法论,10 分钟摸清家底
为什么"陌生应用排障"这么让人崩溃
我以前遇到一些项目,发现他们遇到对自己的应用了解很少:
点开服务器一看,进程名看不懂,目录结构乱七八糟,日志文件几十个,不知道该看哪个。
然后是乱。上来就 tail -f 各种日志,grep 一堆关键字,越查越焦虑,恨不得把整个 /var/log 翻个底朝天。
最后是放弃。折腾半天找不到根因,开始到处问人:"这个服务谁写的?""以前出过类似问题吗?""有文档吗?"
但你发现没,这样做的结果往往是 —— 故障时间越拖越长,最后要么草草重启了事(治标不治本),要么领导亲自下场盯着你查(压力直接拉满)。
后来我才想明白一个道理:
排障不是靠经验碾压,是靠套路。
经验只能让你排查得快一点,但真正让你在陌生系统面前不慌的,是一套可以复用的方法论。今天我就把这套东西掏出来给你。
排障的核心心法:先摸家底,再动手
很多人一上来就埋头看日志,这是大忌。
你连这个服务是干嘛的、用什么语言写的、部署在哪儿、依赖了谁都不知道,就去翻日志,那不叫排障,那叫"算命"。
第一步永远是摸家底。
怎么摸?通过几个问题快速建立认知框架:
这个服务是谁部署的、用什么语言(Java、Go、Python、Node 还是 PHP)?是单实例还是集群部署?跑在物理机、虚拟机还是容器里?
了解这些不是为了装专家,是为了知道接下来该用什么工具去查。比如 Java 应用你得会看 jstack、jmap,Go 应用你得会看 pprof,Python 应用你得知道常见的框架日志格式。
举个最真实的例子:
有一次我接手一个 Python 写的定时任务服务,告警说"任务卡住了不跑"。新人上去就 grep "error",翻了半小时日志一无所获。
我过去一看,先问了一句:"这个服务用的是什么调度框架?"答案是 Celery。
然后我直接去看 Redis 里的任务队列(Celery 默认用 Redis 做 broker),发现任务确实堆在队列里没被消费。接着看 worker 进程日志,发现是连接数据库超时。
整个过程不到 5 分钟。
你看,不了解这个服务的"家底",你根本不知道该看哪里。
所以面对陌生应用,我一般会先做这几件事:
看部署方式
systemd 管理?看 /etc/systemd/system/ 下的 unit 文件,里面有启动命令、工作目录、环境变量。
容器跑的?看 docker inspect 或者 kubectl describe deployment,里面能拿到镜像、挂载、环境变量、命令行参数。
进程直接拉的?看 ps aux | grep 对应的进程,重点看启动命令那一串。
看进程信息
ps -ef 能告诉你进程 PID、启动时间、启动命令。
pstree 能看到这个进程有没有 fork 子进程,子进程在干嘛。
lsof -p <PID> 是神器,能看到这个进程打开了哪些文件、监听了哪些端口、连接了哪些远端 —— 这一条命令能告诉你 80% 的关键信息。
看资源占用
top 或者 htop 看 CPU、内存占用,有没有进程在疯狂吃资源。
iostat、vmstat、netstat 或者现在更推荐用 ss -s,能看网络连接状态、磁盘 IO。
这几个工具一组合起来,这个服务的大致画像就有了:跑在哪儿、用什么语言、连了哪些外部服务、最近有没有重启过。
找入口:摸清请求从哪里进、数据往哪里去
摸完家底,下一步是搞清楚请求的流转路径。
任何线上应用,本质上都是一个数据流转系统:流量从某个入口进来,经过一系列处理(可能调数据库、调缓存、调下游服务、调消息队列),最后返回结果或者写出去。
排障的本质,就是找到哪一环断了。
所以你得先找到入口。
对于 HTTP 服务
入口就是监听端口。看 ss -tlnp 或者 netstat -tlnp,找到进程监听的端口,然后顺着端口找进程,顺着进程找配置。
拿到端口和 IP 之后,自己写个简单的 curl 模拟请求,看返回:
curl -v http://127.0.0.1:8080/health
curl -v -X POST http://127.0.0.1:8080/api/xxx -d '{"key":"value"}'curl -v 非常关键,它会显示完整的请求和响应头,有时候问题就藏在 header 里(比如跨域、Content-Type 不对、认证失败)。
对于定时任务
入口就是调度器。看 crontab、看调度框架的配置(Celery beat、Airflow、xxl-job 这些),找到任务的执行命令和触发周期。
然后手动触发一次任务(如果框架支持的话),看任务能不能正常跑起来。
对于消费 MQ 的服务
入口就是消息队列。看连接的是哪个 MQ(Kafka、RabbitMQ、RocketMQ),用什么 consumer group,消费的是哪个 topic/queue。
用对应的客户端工具(kafka-console-consumer、rabbitmqctl 等)看看队列里有没有积压的消息。
找到入口之后,下一步是追调用链。
这个调用链可能是代码层面的(你得会看代码,至少能看懂流程),也可能是通过网络层面去追踪。
如果你公司接入了分布式追踪系统(Jaeger、Zipkin、SkyWalking 这些),那就太幸福了。直接去 Tracing 系统里搜这个服务的 traceID,能看到完整的调用链路、每个环节的耗时、哪一步报错。
如果没接,那就要靠"网络包分析"了。
tcpdump 抓包是个办法,但生产环境用要谨慎。更好的方式是看应用自己打印的日志,找到请求的 traceID 或者 requestID,然后 grep 这个 ID 把所有相关日志捞出来。
grep "traceID=abc123" /var/log/app/*.log这一招极其好用。哪怕你完全不懂这个应用的代码,也能通过日志把整个请求的"足迹"串起来。
找日志:排障的"主战场"
好,重点来了 —— 怎么找日志。
这是我今天最想跟你唠的。
很多新手找日志的方式是:cd /var/log,然后 ls,看到一个文件就 tail -f,看完没东西再换下一个。
这种方式效率极低,而且经常错过关键信息。
找日志的套路是"先定位范围,再深入细节"。
第一步:日志在哪里?
不同部署方式,日志位置不一样:
systemd 管理的服务:看 unit 文件里的 StandardOutput=journal 还是 StandardError=journal,如果是 journal 就要用 journalctl -u 服务名 来看;如果重定向到了文件,就在 unit 文件里找路径。
容器跑的:看 docker logs <container_id>,或者 kubectl logs <pod_name>。如果用了 sidecar 日志收集,那日志可能不在容器本地,而是被收集到了 ELK、Loki 这些系统里。
进程直接拉的:找启动脚本(一般在 /opt、/home、/srv 这些目录下),脚本里通常会有日志路径的硬编码。或者直接 lsof -p <PID> | grep log,能看到进程当前打开的日志文件。
还有一招特别管用:ls -l /proc/<PID>/fd。这个命令会列出进程打开的所有文件描述符,里面一眼就能看到日志文件在哪个路径。
第二步:日志格式是什么样的?
找到日志文件之后,先别急着 grep。先花两分钟看几条日志,了解它的格式。
日志格式一般包含这些要素:
- 时间戳
- 日志级别(INFO、WARN、ERROR、FATAL)
- 线程名 / 协程名
- 类名 / 函数名 / 代码行号
- 业务相关字段(订单号、用户ID、traceID 等)
- 日志内容
为什么要先看格式?因为你要知道这个应用用什么字段做"唯一标识"。找到了这个标识,你就能精准捞出某一次请求的所有日志。
举几个常见场景:
Java 应用(Log4j/Logback):一般会有 %X{traceId} 这种 MDC 字段。
Go 应用(zap/logrus):通常会有 request_id 或者 trace_id 这种字段。
Python 应用(logging):格式比较自由,但一般也会有自定义的 request_id。
Nginx:access.log 和 error.log 是分开的两份,access.log 里能看到 status、upstream_addr、request_time 这些关键信息。
第三步:按需捞日志
格式搞清楚之后,就可以精准捞日志了。几个高频命令你一定要熟:
# 实时跟踪某个服务的日志
tail -f /var/log/app/service.log
# 看最近 1000 行日志(比 tail 更灵活)
tail -n 1000 /var/log/app/service.log | less
# 按时间范围捞(这个真救命)
sed -n '/2024-01-15 14:00:00/,/2024-01-15 14:30:00/p' /var/log/app/service.log
# 按关键字捞上下文(-A 后几行 -B 前几行 -C 前后各几行)
grep -A 20 -B 5 "OutOfMemoryError" /var/log/app/service.log
# 多个关键字"或"关系
grep -E "ERROR|Exception" /var/log/app/service.log
# 排除干扰信息
grep -v "健康检查" /var/log/app/service.log | grep "ERROR"
# 按业务字段捞(比如捞某个订单的所有日志)
grep "order_id=123456" /var/log/app/service.log
# 统计某个错误出现的次数
grep -c "NullPointerException" /var/log/app/service.log如果你公司有 ELK、Loki、Graylog 这种集中式日志平台,那上面的命令可以换成 Kibana 里的 KQL 语法或者 LogQL,思路完全一样。
第四步:注意日志的坑
找日志不是一帆风顺的,我踩过太多坑了,给你提几个醒:
日志被截断或者丢失。有些应用没有正确处理日志的 rotation,老日志被覆盖;有些容器日志没配持久化,容器一重启就全没了;还有些应用为了性能,把 ERROR 以上的日志直接吞了。
多实例日志分散在不同的机器上。集群部署的服务,每个实例的日志都在不同的服务器上。排查时要把所有实例的日志都捞出来对比,因为问题可能只出在某一个实例上(比如某个实例的 JVM 内存泄漏)。
日志时间和系统时间对不上。容器里时区没配、服务器时间漂移、跨时区部署,这些都会导致日志时间错位。看日志前先 date 一下服务器时间,确保时区一致。
敏感信息被脱敏了。有些应用为了合规,会把日志里的手机号、身份证号、银行卡号这些敏感字段脱敏成 ***。如果你排查的是业务问题,这种脱敏反而会给你带来麻烦 —— 看不到完整数据。
日志量太大,机器卡死。一个高 QPS 的服务,一天能产生几十 G 日志。直接 cat 整个文件可能会把服务器 IO 打爆。这种情况下要用 less、head、tail 这种流式工具,或者直接到日志平台搜。
真实案例:我怎么用这套方法论搞定那次陌生故障
讲理论讲了这么多,给你讲个真实案例,你感受下。
场景:某天上午 10 点,业务反馈"用户支付后,状态一直不更新",订单服务群里炸了。
第一步,看监控
先看监控大盘(Grafana),发现"退款回调服务"的 HTTP 5xx 错误率从 0.1% 飙升到 8%,但 CPU、内存、网络都没异常,机器没崩。
第二步,摸家底
服务器上 ps -ef | grep refund,发现这个服务是用 Java 写的,跑在 K8s 上,2 个副本。
kubectl describe pod refund-callback-xxx,看到镜像名是 refund-callback:v3.2.1,启动命令里带了 -Xmx2g,有个环境变量 PAYMENT_GATEWAY_URL=https://pay.xxx.com。
第三步,找入口
kubectl port-forward 转发端口,本地 curl -v 测了一下,能通但返回 500。看到返回内容是 upstream timeout。
第四步,看日志
kubectl logs refund-callback-xxx --tail=200,看到大量这样的日志:
[ERROR] 2024-01-15 10:05:23 [http-nio-8080-exec-12]
c.b.r.c.PaymentClient - 调用支付通道失败:
SSLHandshakeException: PKIX path building failed第五步,定位根因
SSLHandshakeException、PKIX path building failed,看到这两个关键字我就知道是证书问题。
接着看日志里具体的异常信息:
unable to find valid certification path to requested target这是典型的 Java SSL 证书信任问题。继续往下翻日志,发现第一次出现这个错误的时间是 09:58,跟监控大盘上错误率飙升的时间点完全吻合。
第六步,验证猜想
openssl s_client -connect pay.xxx.com:443 -showcerts,手动去拉支付通道的证书,发现证书链里缺了中间证书(只有叶子证书,没有 CA 证书)。这十有八九是支付通道那边运维换证书的时候,配置出问题了。
第七步,临时止血 + 根治
临时方案:在 Java 应用的信任库里手动补上缺失的中间证书,重启服务,恢复正常。
根治方案:联系支付通道的运维,让他们补全证书链。
整个过程,10 分钟搞定。
复盘一下我做了什么:
- 监控发现异常现象
- 摸清服务家底(语言、部署方式、配置)
- 找到入口并模拟请求
- 精准定位日志
- 从日志中提取关键异常信息
- 用工具验证猜想
- 临时止血 + 根治
这就是方法论的力量。 你不用懂这个服务的代码,不用认识写这个服务的人,不用看过任何文档,照样能定位到根因。
排障前的"救命清单",建议你收藏
我再给你总结一份排障前的 checklist,下次遇到陌生应用,按这个清单一步步走:
摸家底阶段
- 服务部署在物理机/虚拟机/容器?
- 什么语言写的?什么框架?
- 进程名是什么?PID 是多少?
- 启动命令和配置文件在哪?
- 监听了哪些端口?连接了哪些外部服务?
找入口阶段
- HTTP 服务:监听端口 + 域名
- 定时任务:调度器配置 + 执行周期
- MQ 消费:topic/queue + consumer group
- RPC 服务:注册中心 + 服务名
看日志阶段
- 日志文件路径在哪?
- 日志格式是什么?用什么字段做唯一标识?
- 有没有集中式日志平台?
- ERROR 级别的日志说了什么?
- 异常堆栈的第一行(最关键的)
- 异常出现的时间点
- 异常和监控告警的时间点是否吻合
深入排查阶段
- 是资源问题(CPU/内存/磁盘/网络)?
- 是依赖问题(下游服务/数据库/缓存/消息队列)?
- 是配置问题(环境变量/配置文件/证书)?
- 是代码问题(看异常堆栈和代码行号)?
- 是数据问题(脏数据/并发竞争/边界值)?
复盘阶段
- 根因是什么?
- 为什么之前没发现?
- 监控/告警是否合理?
- 如何避免下次再出?
工具和命令速查表
最后再给你列一份我常用的工具清单,建议保存:
系统层面:ps、top、htop、iostat、vmstat、ss、netstat、lsof、strace、tcpdump
进程层面:
- Java:
jps、jstack、jmap、jstat、arthas(强烈推荐,线上排障神器) - Go:
pprof、go tool trace - Python:
py-spy、pyflame
日志层面:tail、grep、less、awk、sed、journalctl
网络层面:curl、telnet、nc、dig、nslookup、openssl s_client、mtr
K8s 层面:kubectl get/describe/logs/exec、kubectl port-forward
集中式日志:
- ELK(Elasticsearch + Logstash + Kibana)
- Loki + Grafana
- Graylog
- 阿里云/腾讯云的日志服务(SLS/CLS)
APM 和 Tracing:
- SkyWalking
- Jaeger
- Zipkin
- 阿里云 ARMS、腾讯云 CAT
写在最后
说到底,陌生应用排障这件事,拼的不是你会不会写代码,也不是你经验有多丰富。
拼的是你面对未知时的"拆解能力"和"套路储备"。
把一个陌生的系统一层层剥开来看 —— 它怎么部署的、入口在哪、数据怎么流、依赖了谁、日志在哪 —— 这些问题回答完了,根因就呼之欲出了。
这套方法论我用了好几年,不管是接手新业务、临时救场、还是面试的时候被问到"你排查过最难的问题是什么",我都能很自信地说出整个流程。
因为我知道,故障是排不完的,但方法论可以复用一辈子。
希望今天这篇能帮到你。如果你身边有刚入行的运维兄弟,把这篇文章转给他,能少走很多弯路。
关注公众号 耕云躬行录,回复 "排障",领取我整理的《线上故障排查标准流程 checklist》和常用命令速查表。
个人博客:躬行笔记,里面记录了更多生产环境的真实踩坑案例,欢迎来逛逛。
下期想看啥?留言告诉我,点赞过 200 我专门写一篇。