本篇做为SpringBoot2.1版本的我的开发框架 子章节,请先阅读SpringBoot2.1版本的我的开发框架再次阅读本篇文章php
后端项目地址:SpringBoot2.1版本的我的应用开发框架html
前端项目地址:ywh-vue-admin前端
在以前的章节咱们测试的时候,发现控台台输出的日志是默认的,而且有不少的日志没有打印,而且不能自定义设置咱们的想要输出的信息,对于一个应用程序来讲日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。vue
对于日志的参考资料网上一搜一大堆,更详细的介绍能够轻松的得到,这里贴出几个参考资料:java
Java有不少经常使用的日志框架,如Log4j、Log4j 二、Commons Logging、Slf4j、Logback等。git
Commons Logging和Slf4j是日志门面,提供一个统一的高层接口,为各类loging API提供一个简单统一的接口。log4j和Logback则是具体的日志实现方案。能够简单的理解为接口与接口的实现,调用者只须要关注接口而无需关注具体的实现,作到解耦。程序员
比较经常使用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用,基于下面的一些优势,选用Slf4j+Logback的日志框架:github
更快的执行速度,Logback重写了内部的实现,在一些关键执行路径上性能提高10倍以上。并且logback不只性能提高了,初始化内存加载也更小了spring
自动清除旧的日志归档文件,经过设置TimeBasedRollingPolicy 或者 SizeAndTimeBasedFNATP的 maxHistory 属性,你就能够控制日志归档文件的最大数量json
Logback拥有远比log4j更丰富的过滤能力,能够不用下降日志级别而记录低级别中的日志。
Logback必须配合Slf4j使用。因为Logback和Slf4j是同一个做者,其兼容性不言而喻。
默认状况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。
由上可知咱们springboot项目默认使用的就是Logback,咱们能够经过设置yml文件的方式来设置日志的格式,也能够经过logback.xml的方式来设置日志管理。
yml方式: 这种方式相对于xml的方式比较简单,由于你不配置,springboot也会有默认的设置,在application-dev.yml开发环境添加如下配置便可生效,path的路径在开发环境时能够是windows下的路径,当你部署到liunx服务器时须要使用application-prod生产环境的配置文件,文件中配置的路径为liunx的路径便可。
logging:
file:
#存放文件的最大天数
max-history: 15
#存放日志最大size
max-size: 100MB
#存放日志文件位置
path: E:\logs
pattern:
#输出到控制台的格式
console: "YWH - %d{yyyy-MM-dd HH:mm:ss} -%-4r [%t] %-5level %logger{36} - %msg%n"
#日志级别映射,能够指定包下的日志级别 也可指定root为info级别
level:
root: info
com.ywh.core: debug
*************************************************
<!-- %d{HH: mm:ss.SSS}——日志输出时间 -->
<!-- %thread [%t] ——输出日志的进程名字,这在Web应用以及异步任务处理中颇有用 -->
<!-- %-4r —— "-"表明了左对齐 将输出从程序启动到建立日志记录的时间 进行左对齐 且最小宽度为4 -->
<!-- %-5level——日志级别,而且使用5个字符靠左对齐 -->
<!-- %logger{36}——日志输出者的名字 -->
<!-- %msg——日志消息 -->
<!-- %n——平台的换行符 -->
<!-- 更多的详情可参考 : https://aub.iteye.com/blog/1103685 此博客最下方有解释 复制代码
如下图片来自于:aub.iteye.com/blog/110368…
xml方式:这种方式须要配置多个标签,相对与yml方式比较麻烦一点,在resources文件下建立logback-spring.xml文件,若是不想把xml文件直接放在resources下的话,须要在yml文件中配置logging.config= 指定位置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,若是设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档若是发生改变,将会被从新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,若是没有给出时间单位,默认单位是毫秒。 当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds">
<contextName>Y-W-H</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。经过定义的值会被插入到logger上下文中。定义后,可使“${}”来使用变量。 -->
<property name="log.path" value="E:/logs/" />
<!--0. 日志格式和颜色渲染 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName ) [%thread] %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
。。。。。。省略代码,具体代码可前往github查看
</configuration>
复制代码
经过以上两种方式的任意一种配置好之后启动项目之后,就会发现咱们已经使用了咱们自定义的输出格式来输出日志了,在咱们指定下的路径下出现了日志文件。
当日志级别设置到INFO级别后,只会输出INFO以上的日志,如INFO、WARN、ERROR,这没毛病,问题是,程序中抛出的异常堆栈(运行时异常)都没有打印了,不利于排查问题。
并且,在某些状况下,咱们在Service中想直接把异常往Controller抛出不作处理,但咱们不能直接把异常信息输出到客户端,这是很是不友好的,并且咱们想要精准的定位错误的所在,这就要咱们本身来定义异常的输出了,而且把错误的异常以咱们以前封装的Result的统一格式返回给前端,因此咱们须要自定义异常以及定义全局异常类,咱们先定义自定义异常类而后再定义全局异常类。
根据菜鸟教程中的异常信息分类,异常分为三种状况
检查性异常:最具表明的检查性异常是用户错误或问题引发的异常,这是程序员没法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常能够在编译时被忽略。
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中一般被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
而咱们所要作的就是继承运行时异常,对此类异常进行自定义处理,在common下exception包中建立MyException类继承RuntimeException。
package com.ywh.common.exception;
/** * CreateTime: 2018-11-21 19:07 * ClassName: MyXiyiException * Package: com.ywh.common.exception * Describe: * 自定义异常,能够throws的时候用本身的异常类 * * @author YWH */
public class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
public MyException(String message, Throwable throwable) {
super(message, throwable);
}
public MyException(Throwable throwable) {
super(throwable);
}
}
复制代码
在common下的utils包中建立MyExceptionUtil工具类快速建立异常类
package com.ywh.common.utils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ywh.common.exception.MyException;
/** * CreateTime: 2018-12-18 22:32 * ClassName: MyExceptionUtil * Package: com.ywh.common.utils * Describe: * 异常工具类 * * @author YWH */
public class MyExceptionUtil {
public MyExceptionUtil() {
}
public static MyException mxe(String msg, Throwable t, Object... params){
return new MyException(StringUtils.format(msg, params),t);
}
public static MyException mxe(String msg, Object... params){
return new MyException(StringUtils.format(msg, params));
}
public static MyException mxe(Throwable t){
return new MyException(t);
}
}
复制代码
建立完自定义异常之后咱们要对自定义异常进行捕获而后处理,这就须要咱们定义全局异常类来进行捕获后进行处理了,在common下exception中添加GlobalExceptionHandler类。
package com.ywh.common.exception;
/** * @Author: YWH * @Description: 全局异常处理类,拦截controller RestControllerAdvice此注解为ResponseBody和ControllerAdvice混合注解 * @Date: Create in 17:16 2018/11/17 */
@RestControllerAdvice
public class GlobalExceptionHandler {
/** * * 全局异常类中定义的异常均可以被拦截,只是触发条件不同,如IO异常这种必须抛出异常到 * controller中才能够被拦截,或者在类中用try..catch本身处理 * 绝大部分不须要向上抛出异常便可被拦截,返回前端json数据,如数组下标越界,404 500 400等错误 * 若是本身想要写,按着如下格式增长异常便可 *HttpMessageNotReadableException */
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/** * 启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法, * 都会做用在 被 @RequestMapping 注解的方法上。 * @param binder */
@InitBinder
public void initWebBinder(WebDataBinder binder){
}
/** * 系统错误,未知的错误 已测试 * @param ex 异常信息 * @return 返回前端异常信息 */
@ExceptionHandler({Exception.class})
public Result exception(Exception ex){
log.error("错误详情:" + ex.getMessage(),ex);
return Result.errorJson(BaseEnum.SYSTEM_ERROR.getMsg(),BaseEnum.SYSTEM_ERROR.getIndex());
}
。。。。。。省略代码,具体代码请前往github查看
/** * 自定义异常信息拦截 * @param ex 异常信息 * @return 返回前端异常信息 */
@ExceptionHandler(MyException.class)
public Result myCustomizeException(MyException ex){
log.warn("错误详情:" + ex);
return Result.errorJson(BaseEnum.CUSTOMIZE_EXCEPTION.getMsg(),BaseEnum.CUSTOMIZE_EXCEPTION.getIndex());
}
}
复制代码
在GlobalExceptionHandler中咱们对不少异常进行了拦截后自定义处理,并把咱们上边自定义的运行时异常进行拦截,我在类中的方法上都写了注释,并根据网上的资料应该很好理解,我对大部分的异常都作了测试,都是能够进行拦截成功的。
咱们用postman经过post方式请求一个get的方法,能够看到返回了咱们自定义的json格式,而且告诉咱们这是由于接口类型所致使的错误,这样咱们很快就能定位到错误进行解决。
以上错误都是系统替咱们捕获而且经过全局异常类进行了拦截以后返回自定义的json格式,而咱们的自定义异常如何使用呢,自定义异常须要咱们手动捕获异常,而且抛出异常,这样咱们的全局异常类才能拦截到。
咱们在ExampleServiceImpl中定义一个方法,并在Controller层中调用此方法,用postman调用此接口
/** * 测试自定义异常 * @return 返回字符串 */
@Override
public String myException() {
int i = 0;
int a = 10;
if( i > a){
System.out.println("测试!!!");
}else{
throw MyExceptionUtil.mxe("出错了,比他小啊!!");
}
return "没有进行拦截,失败了";
}
复制代码
@Autowired
private ExampleService exampleService;
@GetMapping("myExceptionTest")
public Result myExceptionTest(){
return Result.successJson(exampleService.myException());
}
复制代码
能够看到咱们的自定义异常被拦截到而且在控制台中打印了咱们想要的信息。