本地调用没有问题,然而到生产环境就报错这种问题该怎么排查?

149 天前
 tiRolin

最近我负责的某个项目新增了功能,然而发版之后就报错,具体日志报下面的问题

java.lang.NullPointerException
	at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
	at java.util.regex.Matcher.reset(Matcher.java:309)
	at java.util.regex.Matcher.<init>(Matcher.java:229)
	at java.util.regex.Pattern.matcher(Pattern.java:1093)
	at java.util.Formatter.parse(Formatter.java:2547)
	at java.util.Formatter.format(Formatter.java:2501)
	at java.util.Formatter.format(Formatter.java:2455)
	at java.lang.String.format(String.java:2940)
	at com.orgexample.common.core.exception.BusinessException.<init>(BusinessException.java:36)
	at com.orgexample.common.core.exception.BusinessException.<init>(BusinessException.java:26)
	at com.orgexample.tourism.tourism.util.DstZipUtil.compressFileList(DstZipUtil.java:46)
	at com.orgexample.tourism.tourism.versionfile.service.VersionFileService.generateZipFile(VersionFileService.java:142)
	at com.orgexample.tourism.tourism.versionfile.service.VersionFileService.generateVersionZipFile(VersionFileService.java:97)
	at com.orgexample.tourism.tourism.support.ExecutionApiTimerTaskService.generateJsonOfflinePackage(ExecutionApiTimerTaskService.java:331)
	at com.orgexample.tourism.tourism.support.ExecutionApiTimerTaskService.executeTimeTask(ExecutionApiTimerTaskService.java:212)
	at com.orgexample.tourism.tourism.support.ExecutionApiTimerTaskService.updateDstApiManageVersion(ExecutionApiTimerTaskService.java:80)
	at com.orgexample.tourism.tourism.support.ExecutionApiTimerTaskService$$FastClassBySpringCGLIB$$c7558311.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.lang.Thread.run(Thread.java:750)

根据异常,显然定位到发生问题的异常代码在下面的代码中

  /**
   * 批量压缩文件 v4.0
   *
   * @param fileNames  需要压缩的文件名称列表(包含相对路径)
   * @param zipOutName 压缩后的文件名称
   **/
  public static void compressFileList(String zipOutName, List<String> fileNameList) {
    ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("compressFileList-pool-").build();
//    ExecutorService executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), factory);
    ExecutorService executor = Executors.newFixedThreadPool(40, factory);
    ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor);
    try (OutputStream outputStream = new FileOutputStream(zipOutName)) {
      ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream);
      zipArchiveOutputStream.setEncoding("UTF-8");
      for (String fileName : fileNameList) {
        File file = new File(fileName);
        getFiles(parallelScatterZipCreator, file.getName(), file);
      }
      parallelScatterZipCreator.writeTo(zipArchiveOutputStream);
      zipArchiveOutputStream.close();
    } catch (IOException | ExecutionException | InterruptedException e) {
      throw new BusinessException(e.getMessage());
    }
  }

这里麻烦各位先忽略这个代码的槽点吧,我也很想吐槽,但是这是 21 点的代码,不知道是哪位离职的同事写的,改是没法改了,只能将就着看了

由于生产环境不能 Debug 调试,所以只能确定是这里出了空指针异常,但是不知道具体是哪里出了异常,又是因为什么出的异常

我新增的功能联系了三张表,我尝试将这三张表都提取出来,在本地环境还原到没部署前的节点,此时在本地运行该接口,并不会报错,但是在生产环境就报错,而且该问题是在我新版本发版之后产生的,确定过好几遍,数据库是没问题的,几乎是保证和生产环境和本地环境一致了,然而在本地就是没问题,在生产就是有问题

代码问题也排查过了,进入部署的 jar 包中查看发现本次发版部署的代码也存在

生产环境的权限只有运维有权限操作,我一个开发尽可能就不想打印很多日志然后让生产部署上去再运行一遍查看效果就是,所以打印更详细的日志这个方法还没使用

想问问各位知道出现这种问题有什么解决思路吗?我想先试试你们的解决方法,如果能解决就不搞这么麻烦了

2368 次点击
所在节点    Java
19 条回复
wowo243
149 天前
arthas 调试看下入参返回值之类的
noobility
149 天前
异常发生在 com.orgexample.common.core.exception.BusinessException.<init>(BusinessException.java:26),看着是你的 BusinessException 会对传入的 e.getMessage()调用 String.format ,然而这个字符串为 null ,就抛 NPE 了
zbatman
149 天前
看样子像是 BusinessException 本身的异常
liprais
149 天前
NPE 这么好查还不会么,看到哪断了就是那有问题
niurougaodanbaid
149 天前
arthas
pxiphx891
149 天前
报的哪一行,就看哪一行不就行了吗
label
149 天前
增加日志, 把每行结果都打印一下
XXWHCA
149 天前
#2 #3 楼正确答案,e.getMessage()返回值是可为空的,所以进行 format 之前需要判空。
多种异常一起捕获,message 的内容更加不可控,这种情况先打印日志再抛出异常。
chairuosen
149 天前
不知道 java 能不能打印 catch 的那个 e 的 stack ,能打印原始错误栈最好。
另外,最土的办法,但也最实用,在 try catch 外面定义一个变量记录状态,try 里面每一行代码之后改变一下记录的值比如 stage=012345 ,在 catch 时打印这个 stage ,就知道报错时走到哪一步了,另外把中间变量也记住,就能知道具体报错的 fileName 或者 file 是咋回事。
wolfie
149 天前
没人吐槽线程池?
调用一次,创建一个新的线程池,还不关闭。
prosgtsr
149 天前
BusinessException.<init>
看看 new 方法

话说:这是 21 点(年)的代码,不知道是哪位离职的同事写的,改是没法改了,只能将就着看了
我的工作:这一块是 16 年的代码,但是还得改
oliverCC
149 天前
throw new BusinessException(e.getMessage());
从日志上看这行抛出了异常,原因是你的 try catch 中发生了空指针异常,但是你这行代码没有记录日志。
正常写法应该是
log.error(String.format("压缩文件出错%s",e.getMessage()),e);
throw new BusinessException(e.getMessage(),e);

从你现有代码来看,大概率是 循环中的 getFiles 方法内有问题导致的。

之所以生产环境会报错,而本地自测不报错 那是因为程序运行时变量不同(变量包括不限于程序报错时的出入参、机器各项指标参数等 代码相同只是这些变量中的一个)

对于已经出现的这个报错如果不能添加日志来排查,可以看下能否找到这个报错之前的一些出入参和打包的文件,所有变量和本地报错一致自测复现。
如果日志这条路走不通,可以按照楼上说的 安装 arthas 或者 分布式链路追踪 pinpoint 、SkyWalking 、Zipkin 这些工具也可以作为排查的思路。
sampeng
149 天前
昨天还和同事聊 AI 的影响呢,现在就已经很多很多 3-5 年的程序员连怎么调试和分析日志都做不到,或者很慢了。
如果 AI 编程变成非常普遍的工具,AI 能解决的,AI 会分析出来告诉你怎么搞。AI 不能解决的呢?这玩意真的是经验值,不是 AI 训练得出来的。个人感觉排 bug 有点俺寻思之力的味道,就是经验在那里,有时候突然灵光一闪,是和这个 bug 半毛钱关系的地方可能有影响,我不觉得 AI 能分析一个远大过他 token 数目的复杂项目。
xuanbg
149 天前
可能是服务器上没有足够权限
tiRolin
149 天前
@sampeng 这些事情只能靠经验吧,我还在实习,大学期间调过项目,但是基本都是本地的 Bug 很好解决,这种问题是第一次见,我不知道该如何快速解决,我的水平比较低,只能一步一步走
lastexile
148 天前
应该是这里的问题 File file = new File(fileName);
本地调用读的是 windows 的文件路径,
生产读的是 jar 包里的文件的话,是不能这样读文件的,必须要用 resource 来读,类似这样
Resource[] resources = applicationContext.getResources("classpath*:dirname/*.zip");
for (Resource r : resources) {
InputStream is = r.getInputStream()
// do other things
}
lastexile
148 天前
然后就是把这个 e 的调用栈打印出来,有可能是这个 e.getMessage()为空,导致 throw new BusinessException 也抛出空指针了,这是后续引起的问题了,直接问题应该还是 File 对象那里的问题,导致 getFiles 报错
pocketz
148 天前
楼上说的没错,你的原始异常实际上被包在了 BusinessException 内。建议是把原始异常打出来看看
wowo243
148 天前
@wolfie #10 说明他们的服务还没崩,等到他们发现内存泄露的时候他们就会发现了👀

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://yangjunhui.monster/t/1103874

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX