运维知识
悠悠
2026年8月3日

面对完全陌生的线上应用,我靠这套"找日志"方法论,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、内存占用,有没有进程在疯狂吃资源。

iostatvmstatnetstat 或者现在更推荐用 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 打爆。这种情况下要用 lessheadtail 这种流式工具,或者直接到日志平台搜。


真实案例:我怎么用这套方法论搞定那次陌生故障

讲理论讲了这么多,给你讲个真实案例,你感受下。

场景:某天上午 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

第五步,定位根因

SSLHandshakeExceptionPKIX 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/内存/磁盘/网络)?
  • 是依赖问题(下游服务/数据库/缓存/消息队列)?
  • 是配置问题(环境变量/配置文件/证书)?
  • 是代码问题(看异常堆栈和代码行号)?
  • 是数据问题(脏数据/并发竞争/边界值)?

复盘阶段

  • 根因是什么?
  • 为什么之前没发现?
  • 监控/告警是否合理?
  • 如何避免下次再出?

工具和命令速查表

最后再给你列一份我常用的工具清单,建议保存:

系统层面pstophtopiostatvmstatssnetstatlsofstracetcpdump

进程层面

  • Java:jpsjstackjmapjstatarthas(强烈推荐,线上排障神器)
  • Go:pprofgo tool trace
  • Python:py-spypyflame

日志层面tailgreplessawksedjournalctl

网络层面curltelnetncdignslookupopenssl s_clientmtr

K8s 层面kubectl get/describe/logs/execkubectl port-forward

集中式日志

  • ELK(Elasticsearch + Logstash + Kibana)
  • Loki + Grafana
  • Graylog
  • 阿里云/腾讯云的日志服务(SLS/CLS)

APM 和 Tracing

  • SkyWalking
  • Jaeger
  • Zipkin
  • 阿里云 ARMS、腾讯云 CAT

写在最后

说到底,陌生应用排障这件事,拼的不是你会不会写代码,也不是你经验有多丰富。

拼的是你面对未知时的"拆解能力"和"套路储备"。

把一个陌生的系统一层层剥开来看 —— 它怎么部署的、入口在哪、数据怎么流、依赖了谁、日志在哪 —— 这些问题回答完了,根因就呼之欲出了。

这套方法论我用了好几年,不管是接手新业务、临时救场、还是面试的时候被问到"你排查过最难的问题是什么",我都能很自信地说出整个流程。

因为我知道,故障是排不完的,但方法论可以复用一辈子。

希望今天这篇能帮到你。如果你身边有刚入行的运维兄弟,把这篇文章转给他,能少走很多弯路。


关注公众号 耕云躬行录,回复 "排障",领取我整理的《线上故障排查标准流程 checklist》和常用命令速查表。

个人博客:躬行笔记,里面记录了更多生产环境的真实踩坑案例,欢迎来逛逛。

下期想看啥?留言告诉我,点赞过 200 我专门写一篇。

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码