springboot ControllerAdvice lombok+Logback全局异常和日志处理
例子一
1、log的xml文件
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="10 seconds"> <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <contextName>logback</contextName> <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --> <property name="log.path" value="F:/guli_log/edu" /> <!-- 彩色日志 --> <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 --> <!-- magenta:洋红 --> <!-- boldMagenta:粗红--> <!-- cyan:青色 --> <!-- white:白色 --> <!-- magenta:洋红 --> <property name="CONSOLE_LOG_PATTERN" value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/> <!--输出到控制台--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!--输出到文件--> <!-- 时间滚动输出 level为 INFO 日志 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_info.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志归档路径以及格式 --> <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录info级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 WARN 日志 --> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_warn.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录warn级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 ERROR 日志 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_error.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录ERROR级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。 <logger>仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 如果未设置此属性,那么当前logger将会继承上级的级别。 --> <!-- 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: 第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息 第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别: --> <!--开发环境:打印控制台--> <springProfile name="dev"> <!--可以输出项目中的debug日志,包括mybatis的sql日志--> <logger name="com.guli" level="INFO" /> <!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG 可以包含零个或多个appender元素。 --> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <!--生产环境:输出到文件--> <springProfile name="pro"> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="ERROR_FILE" /> <appender-ref ref="WARN_FILE" /> </root> </springProfile> </configuration>
2、自定义异常类
package com.stu.servicebase.exceptionHandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor //生成有参数的注解的构造方法 @NoArgsConstructor //生成无参数的注解的构造方法 public class GuliException extends RuntimeException { private Integer code;//状态码 private String msg;//异常信息 @Override public String toString() { return "GuliException{" + "message=" + msg + ", code=" + code + '}'; } }
3、全局异常处理类
package com.stu.servicebase.exceptionHandler; import com.stu.commonutils.ResultData; import com.stu.servicebase.util.ExceptionUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Slf4j public class GlobalExceptionHandler { //指定出现什么异常执行这个方法 @ExceptionHandler(Exception.class) @ResponseBody public ResultData error(Exception e){ e.printStackTrace(); return ResultData.error().message("执行了全局异常处理"); } //特定异常 @ExceptionHandler(ArithmeticException.class) @ResponseBody public ResultData error(ArithmeticException e){ e.printStackTrace(); return ResultData.error().message("执行了除零异常处理"); } //自定义异常 @ExceptionHandler(GuliException.class) @ResponseBody public ResultData error(GuliException e){ log.error(e.getMessage());//log自带打印方式 log.error(ExceptionUtil.getMessage(e));//自定义打印方式 e.printStackTrace(); return ResultData.error().code(e.getCode()).message(e.getMsg()); } }
例子二
1、自定义异常
package com.stu.service.base.exception; import com.stu.service.base.result.ResultCodeEnum; import lombok.Data; /****************************** * 用途说明:自定义异常 * 作者姓名: Administrator * 创建时间: 2022-04-18 0:00 ******************************/ @Data public class CustomException extends RuntimeException{ private Integer code; public CustomException(ResultCodeEnum resultCodeEnum){ super(resultCodeEnum.getMessage()); //this.message = resultCodeEnum.getMessage(); this.code = resultCodeEnum.getCode(); } }
2、全局异常
注意如果这里和具体的业务的异常重复了,这里就不执行,执行业务里的,比如数字异常,ArithmeticException在全局异常里定义了,service接口也try catch数字异常ArithmeticException了,只走service里的,这里的不走
package com.stu.service.base.handler; import com.stu.service.base.exception.CustomException; import com.stu.service.base.result.R; import com.stu.service.base.utils.ExceptionUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /****************************** * 用途说明:统一异常处理 * 作者姓名: Administrator * 创建时间: 2022-04-17 23:32 ******************************/ @Slf4j @ControllerAdvice public class GlobalExceptionHandler { /*********************************** * 用途说明:自定义异常 * 返回值说明: com.stu.service.base.result.R ***********************************/ @ExceptionHandler(CustomException.class) @ResponseBody public R customException(CustomException e){ // e.printStackTrace(); // log.error(e.getMessage()); log.error(ExceptionUtils.getMessage(e)); return R.error().message(e.getMessage()).code(e.getCode()); } /*********************************** * 用途说明:数字异常 * 返回值说明: com.stu.service.base.result.R ***********************************/ @ExceptionHandler(ArithmeticException.class) @ResponseBody public R arithmeticException(Exception e){ log.error(ExceptionUtils.getMessage(e)); return R.error().message(e.getMessage()); } /*********************************** * 用途说明:全局异常 * 返回值说明: com.stu.service.base.result.R ***********************************/ @ExceptionHandler(Exception.class) @ResponseBody public R error(Exception e){ log.error(ExceptionUtils.getMessage(e)); return R.error().message(e.getMessage()); } }
3、应用
package com.stu.service.vod.service.impl; import com.aliyun.vod.upload.impl.UploadVideoImpl; import com.aliyun.vod.upload.req.UploadStreamRequest; import com.aliyun.vod.upload.resp.UploadStreamResponse; import com.stu.service.base.exception.CustomException; import com.stu.service.base.result.ResultCodeEnum; import com.stu.service.vod.service.VodService; import com.stu.service.vod.utils.ConstantVodIInit; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; /****************************** * 用途说明: * 作者姓名: Administrator * 创建时间: 2022-06-07 22:01 ******************************/ @Service @Slf4j public class VodServiceImpl implements VodService { /*********************************** * 用途说明:视频点播 * @param file * 返回值说明: * @return java.lang.String ***********************************/ @Override public String uploadVideo(MultipartFile file) { final String keyId = ConstantVodIInit.KEY_ID; final String keySecret = ConstantVodIInit.KEY_SECRET; try { //文件的原始名称 test.mp4 String originalFileName = file.getOriginalFilename(); //inputStream:上传文件输入流 InputStream inputStream = file.getInputStream(); //文件的标题 test String title = originalFileName.substring(0, originalFileName.lastIndexOf(".")); UploadStreamRequest request = new UploadStreamRequest(keyId, keySecret, title, originalFileName, inputStream); UploadVideoImpl uploader = new UploadVideoImpl(); UploadStreamResponse response = uploader.uploadStream(request); String videoId = response.getVideoId(); //请求视频点播服务的请求ID if (StringUtils.isEmpty(videoId)) { log.error("视频上传失败: " + response.getMessage() + "-" + response.getMessage()); throw new CustomException(ResultCodeEnum.VIDEO_UPLOAD_ALIYUN_ERROR); } return videoId; } catch (IOException e) {//如果这里也定义数字异常ArithmeticException了,只走这里,就不走上边全局里的ArithmeticException异常了 e.printStackTrace(); log.error("视频上传失败: " + e.getMessage()); } return null; } }