你以为程序员只是闷着头疯狂写bug,写好了发布到服务器就完了?
不,你还要修bug!但在那之前,你还要找bug!
那么问题来了,程序都部署都服务器了,怎么找bug呢?下面给大家介绍几种方法,帮助你轻松找到程序的bug。
日志篇
在上篇文章中,笔者介绍了《在Java代码中打日志需要注意什么?》。打日志是非常重要的,因为日志能够提供一些运行时的信息,非常有助于我们快速定位问题。
那么怎么通过日志去分析问题呢?我们首先从单机服务器说起,因为有时候可能bug是测试发现的,而测试环境一般机器都比较少,可能不会专门搭建ELK等日志收集器,所以可能是要到服务器上去看日志。
统一日志配置标准
这个其实是非常重要的,因为我们首先要找到日志文件的路径,才能找到日志进行分析。尤其在一个大点的团队,服务非常多,比较忌讳每个服务的日志文件路径乱放,找半天找不到日志放在哪。
所以建议团队有一个统一的日志配置标准。不论是格式、路径、保存日期和清理策略等,最好都保持一致,并写进团队的建设文档里,这样开发人员才能够快速地找到自己想要的日志。
分析日志常用命令
现在服务端大多部署在Linux服务器上,所以这里介绍一些在Linux上常用的分析日志的命令。
tail
tail
命令是笔者最常用的命令。我们知道,日志文件大多都是追加的方式进行的。所以最新发生的问题一般都是在日志文件的末尾。tail
命令可以打印出日志倒数第n行的日志,还可以通过-f
参数来实时追踪。
# 打印x.log的末尾1000行日志,默认是10 tail x.log -n1000; # 打印x.log末尾10行日志,并继续打印出后面新增的日志 tail x.log -f # 这个跟上面是等价的 tailf x.log
cat + grep
上面介绍的tail
命令,可以输出文件的末尾n行。但有时候我们发现问题时已经有点晚了,这个时候可以在整个日志文件里面找。
通过cat
命令,可以打印出整个日志文件的内容。但整个日志文件内容太多了,所以我们通常需要通过管道加上grep
来结合,只提取我们需要的信息。
cat x.log | grep 'xxx' 复制代码
这里grep
的内容“xxx”,通常建议以下值:
- 日志中的数据的id或者uid,比如43432413;
- 日志中的traceId;
- 日志中的问题描述,比如“call userService failed”;
- 日志中的文件,比如“UserServiceImpl”
当然了,这里的管道和grep
,也可以和上面的tail
命令相结合。另外,如果grep出来的内容比较多,后续还想分析,管道还可以将输出流输出到一个文件里,比如:
cat x.log | grep 'xxx' > temp.log
输出到一个单独的文件里了,我们就能够对它进行下一步分析了,比如发生这个异常的数据有多少条,提取里面的关键信息。还可以通过sed
命令来做。
比如:
# 打印temp.log $ cat temp.log error data: 124 error data: 121j error data: 124fdsfsd # 用正则提取里面的id $ cat temp.log | sed -r "s/error data: (.*)/\1/g" 124 121j 124fdsfsd
vi
cat + grep虽然好,但是不是很方便查看异常周围的日志。比如我们想查看完整的日常堆栈,可以通过vi的搜索功能来做。
vi是在Linux平台上常用的文本编辑器,功能非常强大。当然,很多朋友更习惯用功能更强的vim,用法是类似的。今天要介绍的是它的一些常用的搜索功能和跳转功能。
# 从前到后搜索 /searchKey # 从后到前搜索 ?SearchKey # 定位光标到下一个搜索的地方 快捷键n # 定位到光标上一个搜索的地方 快捷键N # 设置显示行号 :set nu 复制代码
以上差不多能够在服务器上定位和分析日志了。如果你的服务器是分布式的,部署在很多台机器上,比如UAT环境和生产环境,那其实不建议再去服务器上手动分析日志,而是用成熟的日志框架去收集和分析日志。
比较常用的开源日志收集框架是ELK、Splunk等,它们都提供了web界面和丰富的查询语句支持。
远程Debug篇
现在JVM支持远程调试。其原理是因为Java是跨平台的,只要远程的字节码文件和本地的字节码文件相同,两个JVM可以通过调试协议进行通信。
❝所以这里需要保证本地和服务端的代码要一致,不然可能导致某个端点无法进入。尤其使用分支开发的同学需要注意分支是否一致
❞
IDEA上有一个非常好用的远程Debug工具,下面简单介绍一下如何在IDEA上如何Debug。首先通过Run -> EditConfigurations进去,然后添加一个Remote。
然后配置一下host和端口就行了,这里需要注意的是端口是服务器上面的新端口,不要与服务本身的web端口冲突了,可以在应用启动时传入下图中的JVM参数指定端口和远程debug配置。
remote debug
创建好以后,在IDEA打开你的远程debug,就可以愉快地Debug了。
❝不推荐在生产环境使用远程Debug
❞
神器Arthas
Arthas是阿里开源的一款Java诊断工具,功能非常强大。官网上有一个在线的交互式教程,几分钟入门,非常方便。支持命令的自动补全、历史命令补全,管道等。这里介绍它的一些常用的功能。
Dashboard
Arthas的dashboard
命令可以显示当前系统和JVM的实时状态。包括线程的状态、JVM的状态。
dashboard
查看线程
通过thread 1
可以打印「线程id」为1的栈。而且Arthas也支持管道,可以对输出通过管道进行grep:
[arthas@37]$ thread 1 "main" Id=1 TIMED_WAITING at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at demo.MathGame.main(MathGame.java:17) Affect(row-cnt:0) cost in 9 ms. [arthas@37]$ thread 1 | grep main( at demo.MathGame.main(MathGame.java:17)
查看方法的运行时信息
通过watch
命令可以查看方法的运行时参数/返回值/异常信息。
[arthas@37]$ watch demo.MathGame primeFactors returnObj Press Q or Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 40 ms. ts=2020-05-10 02:53:46; [cost=1.129697ms] result=null ts=2020-05-10 02:53:47; [cost=0.056254ms] result=null ts=2020-05-10 02:53:48; [cost=0.056249ms] result=null ts=2020-05-10 02:53:49; [cost=0.210126ms] result=@ArrayList[ @Integer[2], @Integer[3], @Integer[5], @Integer[1039], ] ts=2020-05-10 02:53:50; [cost=0.204834ms] result=@ArrayList[ @Integer[3], @Integer[3], @Integer[3], @Integer[7], @Integer[839], ]
动态执行代码
这是我觉得Arthas里最强大的一个功能了!使用ognl
命令可以让你的应用代码执行执行任意方法,用你制定的任何参数!比如:
[arthas@37]$ ognl '@java.lang.System@out.println("hello ognl")'
这个时候,你的应用代码会输出“hello ognl”。
Arthas还有很多非常强大的功能,它甚至可以热更新代码,就是动态替换你想替换的某个类!
当然,笔者觉得Arthas的命令强大归强大,使用起来还是有一些复杂的,毕竟是一个基于命令行的程序。IDEA有一个arthas idea插件,可以生成arthas命令,有兴趣的同学可以试试~
arthas idea
以上就是关于如何更快更方便地找bug的介绍。早一秒找到bug,就早一秒挽救损失,说不定就能升职加薪哦~