开发过程中常常会有已处理或无法处理的异常,若我们没有一个明确的处理逻辑和提示信息,在出现异常的时候就很难定位到具体的出错点。
大纲:Java异常体系、异常处理、日志规约、错误码规约、异常与日志综合实践
日志不规范,排查两行泪
- 异常应当描述导致当前异常发生的原因
- 根据异常栈快速定位到异常发生的位置
- 结合异常描述和异常栈解决异常
throwable时所有异常和error的最高父类,需要谨慎使用
java异常体系
非受检异常(RuntimeException,unchecked异常):在个人可控范围之内的
受检异常(checked异常):在个人可控范围之外的,比如飞机延误、数据库异常
异常抛出与捕获的原则:
- 非必要不使用异常
- 使用描述性消息抛出异常
- 力所能及的异常一定要处理
- 异常忽略要有理有据
在finally中修改变量的值并不能改变try{}中已经放入方法返回值中的变量值
在finally中使用return将改变try{}中已经放入方法返回值中的变量值
特殊NPE场景及其处理对策
连续的属性调用易产生NPE,如何防止:
foreach遍历集合的异常
- 不要再foreach循环里进行元素的remove/add操作
- foreach循环会自动跳过遍历空集合,如果对于有null值的集合,碰到null时要注意NPE
日志规约
日志的功能:
- 监控告警:健康检查、指标监控(告警)
- 记录行为轨迹:指标监控(查询)、链路追踪
- 快速定位问题:日志
日志时效规约:
- 当天日志命名:以“应用名.log”来保存
- 过往日志命名:以{logname}.log.{保存日期}命名,日期格式yyyy-MM-dd
- 日志文件至少保存15天:便于排查某些以“周”为频次发生的异常
- 敏感操作信息联机存储6个月:网络安全相关法律的规定。要多机备份,最好使用warn日志级别输出
日志记录规约:
- 系统应依赖使用日志框架(SLF4J、JCL)的API而不是具体日志库中的
- 在日志输出时,字符串变量之间的拼接使用占位符的方式
- 日志打印时禁止直接使用JSON公爵将对象转换成String
- 尽量用英语来描述日志错误信息
logback框架使用之核心配置对象及属性分析
日志输出规约:
- 日志级别开关判断:对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断
- 异常日志信息要完整:应包括一下两类信息:案发现场信息、异常堆栈信息
- 避免重复打印日志
扩展日志的设计与规约
- 扩展日志单独存储:应用中的扩展日志(如打点、临时监控、访问日志等)应单独存储
- 错误日志单独存储:业务日志与错误日志分开存储
错误码规约:
- 定义时要有字母也要有数字
- 要分级分类管理
- 不能直接输出给用户作为提示信息使用
- 不要与业务架构或者组织架构挂钩
- 使用者避免随意定义新的错误码
- 便于不同语言的开发者之间协作
异常处理与日志综合实战
- 在Contreller层统一捕获异常
- 全局异常处理组件GlobalExceptionHandler的定义和使用
- API层异常设计实践
- Service层异常设计实践
- DAO数据处理曾异常、日志实践
- 通用DaoException
- 框架层面有选择性的记录数据操作,比如原始SQL语句以及操作时间
- 使用MDC实现轻量级调用链路追踪
- 分布式链路追踪:将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等
- 链路追踪主要功能:故障快速定位、链路性能可视化、链路分析
- 用有限的异常类处理业务中复杂多变的无限可能
- 通用ServiceException
- 综合ErrorCode
- 降低系统的维护难度与过度设计、冗余的手段