专栏首页求道无异常日志,就不能排查问题了???

无异常日志,就不能排查问题了???

有道无术,术尚可求也!有术无道,止于术!

小声逼逼

众所周知,日志是排查问题的重要手段。关于日志设计,以及怎么根据从【用户报障】环节开始到秒级定位问题这个我们下一期说(绝非套路),这一期,主要讲一下,在没有异常日志的情况下,如何定位问题。没有日志当真能排查问题,不会是标题党吧!

案例一

从最大的同性交友网站中拉取【dubbo-spring-boot-project】的代码。

然后把demo跑起来。

本场景是由真实案例改编,因为公司代码比较复杂也不方便透露,而这个demo在github上大家都能找到,既保证了原汁原味,又能让大家方便自己体验排查过程。

好了,我们先设置owner = "feichao",然后看一下控制台

一切正常

那么,当我设置成owner = "feichaozhenshuai!",再启动

看似一切都正常,那么,我们到控制台一看。

什么情况,怎么就没owner了?

这是在哪个环节出问题了?其实肥朝当初在公司遇到这个问题的时候,场景比这个复杂得多。因为公司的业务里没有owner的话,在运行时会出现一些其他异常,涉及公司业务这里就不展开了,我们言归正传,为毛我设置成feichaozhenshuai!就不行了,那我设置成肥朝大帅比电脑会不会爆炸啊???

常见的错误做法是,把这个问题截图往群里一丢,问“你们有没有遇到过dubbo里面,owner设置不生效的问题?”

而关注了肥朝公众号的【真爱粉丝】会这么问,“dubbo里面设置owner却不生效,你们觉得我要从个角度排查问题?”。一看到这么正确的提问方式,我觉得我不回复你都不好意思。好了,回到主题,这个时候,没有一点点错误日志,但是却设置不成功,我们有哪些排查手段?

套路一

直接找set方法,看看是不是代码做了判断,防止在owner字段里面set类似肥朝真帅这种词语,避免把帅这件事走漏风声!。这么一分析似乎挺有道理对吧,那么,如何快速找到这个set方法呢?如图

public void setOwner(String owner) {
    checkMultiName("owner", owner);
    this.owner = owner;
}

我们跟进checkMultiName代码后发现

protected static void checkProperty(String property, String value, int maxlength, Pattern pattern) {
    if (StringUtils.isEmpty(value)) {
        return;
    }
    if (value.length() > maxlength) {
        throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" is longer than " + maxlength);
    }
    if (pattern != null) {
        Matcher matcher = pattern.matcher(value);
        if (!matcher.matches()) {
            throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" contains illegal " +
                    "character, only digit, letter, '-', '_' or '.' is legal.");
        }
    }
}

从异常描述就很明显可以看出,原来owner里面是只支持-_等这类特殊符号,!是不支持的,所以设置成不成功,和肥朝帅不帅是没关系的,和后面的!是有关系的。擦,原来是肥朝想多了,给自己加戏了!!!

当然肥朝可以告诉你,在后面的版本,修复了这个bug,日志会看得到异常了。这个时候你觉得问题就解决了?

我相信此时很多假粉就会关掉文章,或者说下次肥朝发了一些他们不喜欢看的文章(你懂的)后,他们就从此取关,但是肥朝想说,且慢动手!!!

你想嘛,万一你以后又遇到类似的问题呢?而且源码层次很深,就不是简单的搜个set方法这么简单,这次给你搜到了set方法并解决问题,简直是偶然成功。因此,我才多次强调,要持续关注肥朝,掌握更多套路。这难道是想骗你关注?我这分明是爱你啊!

那么,万一以后遇到一些吞掉异常,亦或者某些原因导致日志没打印,我们到底如何排查?

套路二

我们知道idea里面有很多好用的功能,比如肥朝之前的【看源码,我为什么推荐IDEA?】中就提到了条件断点,除此之外,还有一个被大家低估的功能,叫做异常断点

肥朝扫了一眼,里面的单词都是小学的英语单词,因此怎么使用就不做过多解释。遇到这个问题时,我们可以这样设置异常断点。

运行起来如下:

这样,运行起来的时候,就会迅速定位到异常位置。然后一顿分析,应该很容易找出问题。

是不是有点感觉了?那我们再来一个题型练习一下。

案例二

我们先在看之前肥朝粉丝群的提问

考虑到部分粉丝不在群里,我就简单描述一下这个粉丝的问题,他代码有个异常,然后catch打异常日志,但是日志却没输出。

当然你还是不理解也没关系,我根据该粉丝的问题,给你搭建了一个最简模型的demo,模型虽然简单,但是问题是同样的,原汁原味,熟悉的配方,熟悉的味道。git地址如下:【https://gitee.com/HelloToby/springboot-run-exception】我们运行起来看一下

@Slf4j
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {

    public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {
    }

    @Override
    public void starting() {

    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        throw new RuntimeException("欢迎关注微信公众号【肥朝】");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
    }
}

你会发现,一运行起来进程就停止,一点日志都没。绝大部分假粉丝遇到这个情况,都是菊花一紧,一点头绪都没,又去群里问”你们有没有遇到过,Springboot一起来进程就没了,但是没有日志的问题?“。正确提问姿势肥朝已经强调过,这里不多说。那么我们用前面学到的排查套路,再来走一波

我们根据异常栈顺藤摸瓜

我们从代码中看出两个关键单词【reportFailure】、【context.close()】,经过断点我们发现,确实是会先打印日志,再关掉容器。但是为啥日志先执行,再关掉容器,日志没输出,容器就关掉了呢?因为,这个demo中,日志是全异步日志,异步日志还没执行,容器就关了,导致了日志没有输出。

该粉丝遇到的问题是类似的,他是单元测试中,代码中的异步日志还没输出,单元测试执行完进程就停止了。知道了原理解决起来也很简单,比如最简单的,跑单元测试的时候末尾先sleep一下等日志输出。

在使用Springboot中,其实经常会遇到这种,启动期间出现异常,但是日志是异步的,日志还没输出就容器停止,导致没有异常日志。知道了原理之后,要彻底解决这类问题,可以增加一个SpringApplicationRunListener

/**
 * 负责应用启动时的异常输出
 */
@Slf4j
public class OutstandingExceptionReporter implements SpringApplicationRunListener {

    public OutstandingExceptionReporter(SpringApplication application, String[] args) {
    }

    @Override
    public void starting() {

    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        if (exception != null) {
            log.error("application started failed",exception);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                log.error("application started failed", e);
            }
        }
    }
}

再啰嗦一句,其实日志输出不了,除了这个异步日志的案例外,还有很多情况的,比如日志冲突之类的,排查套路还很多,因此,建议持续关注,每一个套路,都想和你分享!

什么是编程思想?

肥朝始终觉得,要想比别人更优秀,除了比别人更努力这个必要因素外,思维方式,也是我们必要关注的一个重点。比如在案例二中,很多同学知道了bug之后,就认为自己学到东西了,其实这个想法既正确,也不正确。

正确的地方在于,你知道了这个bug,后面遇到相同的问题,你会猜一下是不是同样的原因。

不正确的地方在于,你只知道了这个bug出现的某个场景,但是当我们遇到这个问题,应对的排查套路有哪些你并不知道。也就是说,如果这个问题过后,你排查问题的套路并没有增加,亦或者你没有能从这个问题上,发散出自己的想法,继续压榨出更多的价值,本质上,你的编程能力,其实并没有提升的。

然而,你一旦在公司时间长了,也就是我们常说的老油条,对公司的某些坑熟悉,新人遇到问题时,就容易猜对可能是某个坑。但是其实你的套路来来去去就那几个,本质上你的编程能力并没有提升,却让你产生了自己越来越牛逼,这下必须要加薪的错觉。

一个公司总是有线上报障是有问题的,但是一直不出问题也有问题的。当然很多时候,排查的机会或许轮不到你。这个时候,就会有常见的几种做法。

1.公司确实项目太简单,基本没有什么拿得出手的bug,都是一些低级的漏掉配置的bug。

2.大佬们在排查,反正不是我的问题,那我就看群吹吹水,下班美滋滋。

3.大佬们在排查,等他们有结论了,我就过去问一句是啥问题,然后暗自记下来,下次面试的时候就说是自己排查的,吹一波,美滋滋。

4.大佬们在排查,得知原因后,深入思考,大佬们为啥会想到是这个原因,他们是怎么排查的?用了哪些排查工具?排查技巧?然后暗自总结一波,并把自己代入场景,脑补一下自己来排查问题,并把这个bug压榨出更多价值!(怎么压榨出更多价值,可以查看肥朝之前的源码实战文章,每一篇都有一个环节专门讲拓展思考的)

你的思维方式,你的行动,往往就决定你成为什么样的人。肥朝也始终相信,时间在哪,行动在哪,成就就在哪。一起共勉。

才疏学浅,如果文章中理解有误,欢迎大佬们私聊指正!欢迎关注作者的公众号,一起进步,一起学习!

❤️「转发」和「在看」,是对我最大的支持❤️

本文分享自微信公众号 - JAVA程序狗(javacxg)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 企业是如何从头开发一个商业项目的?

    对于还没有参与过项目的同学,大都与企业项目开发的流程都感到特别的好奇!项目对于程序员来说像是自己的孩子,自己看着一步一步成熟,完善!最后到独立的运行!然后大多数...

    止术
  • JAVA 初学者的编码规范一:命名风格与代码格式

    其实对于初学者,最忌讳的一个事就是在学习过程中,认为自己是新手,代码可以随便写!但是事实上并不是如此!因为一个人的编码规范是从开始学的时候就必须要练习和养成习惯...

    止术
  • SpringBoot对全局异常的处理封装

    springMvc为我们提供的全局异常处理很好的解决了我们在开发中对于异常的捕获与控制,他能够监控所有的@Controller注解类,并提供异常捕捉与处理!

    止术
  • loadrunner 脚本开发-int型变量和字符串的相互转换

    Action2.c(5): Notify: Saving Parameter "param = 12345".

    授客
  • ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.NET MVC

    我们不仅可以创建相应的模板来根据Model元数据控制种类型的数据在UI界面上的呈现方法,还可以通过一些扩展来控制Model元数据本身。在某些情况下通过这两者的结...

    蒋金楠
  • C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)

       IEnumerable、IEnumerator到现在为止对这两个接口还是不太理解,不理解但是自己总是想着试着要搞明白,毕竟自己用的少,所以在此先记录一下。...

    aehyok
  • this关键字

    爱学习的孙小白
  • Python解读:地摊经济火了,你想好摆摊去卖什么了吗?

    【导语】:今天我们来聊聊地摊经济,Python技术部分请看第四部分。公众号后台,回复关键字“地摊”获取完整数据。

    CDA数据分析师
  • PHP设计模式之装饰者模式 转

    装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

    双面人
  • 社区leaf学习笔记|03. 调试Game、Login模块收发消息

    李海彬

扫码关注云+社区

领取腾讯云代金券