Day05 异常处理与日志

今天是孤尽T31训练营第五天,老师讲授了异常处理与日志的知识
强烈推荐孤尽老师写的《Java开发手册》

规约保证健壮性,少出BUG

Java异常体系

使用异常为系统保驾护航

异常应当描述导致当前异常发生的原因

根据异常栈快速定位到异常发生的位置

结合异常描述和异常栈解决异常

很好地处理异常和日志,是企业级项目跟Demo项目的分水岭

C语言的“异常”烦恼

l 代码可读性差
l 当返回值与异常值相近时,容易混淆
l 需要调用方来分析异常,增加多余的工作量

JAVA异常处理流程
请添加图片描述
没有try,交由JVM默认处理
写了try,但是有些异常没有捕获,会走到兜底的程序代码finally,没有finally处理,交由JVM默认处理
异常全部捕获,仍然会走finally。执行完毕,finally中有异常,且没有合理的捕获处理。交由JVM默认处理
异常没有对等捕获或真实的处理,导致finally中仍有异常,程序中埋了雷
合理处理异常,程序少埋雷,review代码需要关注
避免漏网之鱼,对后续程序产生影响

异常处理

捕获异常:不想/没能力处理向上抛出;
抛出异常比较少,同样的异常出现多次,可以在方法定义时声明异常

将方法的正确返回和异常返回分开处理,正确返回用return,异常返回用throw

try抛出多种异常,如何捕获
共同父类异常,处理流程一致:可以捕获一次父类异常
共同父类异常,处理流程不一致:加多个catch,低层异常先抛出,再提升异常等级()
不同父类异常,处理流程一致:用 或 连接多个异常
不同父类异常,处理流程不一致:对等catch处理

枝繁叶茂的JAVA异常体系

谨慎使用Throwable,合理地使用能避免错误和异常直接露出

【强制】在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable类来进行拦截。
使用不当时,捕获Error无法处理,错误地使用会导致程序终止

Error:致命错误,不可控不可处理
JVM异常,堆栈溢出、内存溢出

Exception:不致命,能自动恢复

非受检异常:可控范围 RuntimeException Unchecked异常
可预测异常、需捕获异常、可透出异常

受检异常:可控范围外,可提前计划处理 Checked异常
引起注意型、坦然处置型

受检怎么理解
针对Java编译器来说,编译时会提示的就是 受检异常
checked异常,比如IO异常,数据库异常等。需要提前检查,不做处理编译无法通过
编译器一般都有自动提醒,需要你处理,try 或者 throws


异常处理设计与实践

异常抛出与捕获的原则

try…catch…finally
try with resource(JDK7)
特殊异常NPE场景及其处理对策

异常抛出与捕获的原则**

非必要不使用异常

区分出稳定性代码,不放在try中

误区:天王盖地虎,一个方法一个try。但是可能发生多个异常,但只捕获一个
造成异常向上抛出,导致程序终止。

稳定代码要养成习惯不去catch。因为catch就需要MONITER ERNTER和monitor exit, 不需要这样无谓的成本
异常创建和抛出消耗性能,使用条件判断解决节省性能。
写代码需要自信的态度。形式的全覆盖,并无必要

【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。
对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。

使用描述性消息抛出异常

封装异常上下文参数(操作、特殊指数值、局部变量、运行时间、环境…)

力所能及的异常一定要处理

本层能处理的异常不要抛给上层处理,本层(服务)最了解该怎样处理该异常

上层依赖调用方不知道如何处理或不恰当

异常忽略要有理有据

重视异常可以捕获的信息

捕获异常之后,catch块什么也没有写,异常可以捕获的信息被忽略了
跟没捕获区别不大,异常造成程序的终止,对异常解决没有益处

日志规约

• 日志的功能
使用日志提高系统运营与维护的能力
监控告警:
健康检查、指标监控(告警)
记录行为轨迹:指标监控(查询)、链路追踪
快速定位问题:日志

• 日志时效规约
当天日志命名:以“应用名.log”来保存
过往日志命名:以{logname}.log.{保存日期}命名,日期格式yyyy-MM-dd

对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,过往日志
格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
正例:以 aap 应用为例,日志保存在/home/admin/aapserver/logs/aap.log,历史日志名称为aap.log.2016-08-01

日志文件至少保存15天:便于排查某些以“周”为频次发生的异常

【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

敏感操作信息联机存储6个月:网络安全相关法律的规定。要多机备份,最好使用warn日志级别输出

【强制】根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且进行网络多机备份。

发生纠纷打官司能用

记录用户敏感信息或者操作的日志,并多机备份?该类日志最好使用
对这类敏感信息,最好使用WARN日志级别输出

不要使用ERROR级别:ERROR会有一个独特的处理机制——即时通知机制

• 日志记录规约

  1. 系统应依赖使用日志框架(SLF4J、JCL)的API而不是具体日志库中的
  2. 在日志输出时,字符串变量之间的拼接使用占位符的方式
  3. 日志打印时禁止直接使用JSON工具将对象转换成String
  4. 尽量用英语来描述日志错误信息
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、JCL--Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。
说明:如果对象里某些 get 方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法。

【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用
中文描述即可,否则容易产生歧义。
说明:国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。

logback框架使用之核心配置对象及属性分析

// 入口 getLogger
@Slf4j
public class CatchExceptionTest {
    private static final Logger logger = LoggerFactory.getLogger(CatchExceptionTest.class);
// 入口 getLogger getILoggerFactory
public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());
    if (DETECT_LOGGER_NAME_MISMATCH) {
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
            Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
        }
    }

    return logger;
}
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
    public final Logger getLogger(Class<?> clazz) {
        return this.getLogger(clazz.getName());
    }

    public final Logger getLogger(String name) {

LoggerFactory.class

// 入口 getLoggerFactory
public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == 0) {
        Class var0 = LoggerFactory.class;
        synchronized(LoggerFactory.class) {
            if (INITIALIZATION_STATE == 0) {
                INITIALIZATION_STATE = 1;
                performInitialization();
            }
        }
    }

    switch(INITIALIZATION_STATE) {
    case 1:
        return SUBST_FACTORY;
    case 2:
        throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
    case 3:
        // 获取日志工厂
        // 指定StaticLoggerBinder,具体实现和加载的是logback-class的实现类
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case 4:
        return NOP_FALLBACK_FACTORY;
    default:
        throw new IllegalStateException("Unreachable code");
    }
}

定位到了 logback-classic
请添加图片描述

StaticLoggerBinder是logback-classic的具体实现

// StaticLoggerBinder
public ILoggerFactory getLoggerFactory() {
    if (!this.initialized) {
        return this.defaultLoggerContext;
    } else if (this.contextSelectorBinder.getContextSelector() == null) {
        throw new IllegalStateException("contextSelector cannot be null. See also http://logback.qos.ch/codes.html#null_CS");
    } else {
        return this.contextSelectorBinder.getContextSelector().getLoggerContext();
    }
}
日志输出规约

日志级别开关判断
对于 trace/debug/info 级别的日志输出,必须进 行日志级别的开关判断

异常日志信息要完整
异常日志信息应该包括 以下两类信息: 案发现场信息,异常堆栈信息

避免重复打印日志
重复打印日志,浪费磁盘空间,务必在日志配置文件中设置additivity=false


扩展日志与规约

扩展日志单独存储

应用中的扩展日志(如打点、临时 监控、访问日志等)单独存储

错误日志单独存储

业务日志与错误日志分开存储

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值