定义报错的日志级别枚举
import lombok.Getter;
@Getter
public enum ExceptionLogLevelEnum {
//抛出错误时打印的错误日志级别
ERROR(),
WARN(),
}
项目中定义不同的错误枚举
import lombok.Getter;
/**
* job=300000-399999
* 业务异常枚举
*/
@Getter
public enum JobErrorEnum implements ErrorEnum {
/**
* 通用异常枚举
*/
SYSTEM_ERROR(300000,"网络超时,请稍后重试!"),
PARAM_NULL_ERROR(300001,"参数为空异常!"),
Enum_NULL_ERROR(300002,"该值不存在!"),
GENERATE_FAILURE_ERROR(300003,"生成失败!"),
NOT_LOGIN(300004,"用户未登陆!"),
UPLOAD_ATTACHMENT(300005,"请先上传附件!"),
REDIS_NULL_ERROR(300006,"该值在redis中不存在!"),
STRING_DECRYPT_ERROR(300007, "密文解密失败"),
FILE_READ_ERROR(300008, "文件读取失败"),
;
/**
* 错误码
*/
private Integer code;
/**
* 错误描述
*/
private String msg;
JobErrorEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public String getCodeStr(){
return this.code.toString();
}
}
定义基本错误捕捉类,根据日志级别的不同打印日志
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BaseGlobalExceptionHandler {
/**
* 继承BaseException的异常都可以自动按级别记错误日志
* @param s
* @param ex
* @return void
* @author linton.cao
* @date 2021/9/4 20:35
*/
public static void log(String s, BaseException ex){
switch (ex.getErrorLevel()) {
case WARN:
log.warn(s, ex);
break;
case ERROR:
default:
log.error(s, ex);
}
}
/**
* 其他类型异常需要指定日志级别
* @param s
* @param ex
* @param level
* @return void
* @author linton.cao
* @date 2021/9/4 20:35
*/
public static void log(String s, Throwable ex, ExceptionLogLevelEnum level){
switch (level) {
case WARN:
log.warn(s, ex);
break;
case ERROR:
default:
log.error(s, ex);
}
}
}
继承基本错误处理类,实现对于各种错误处理的操作,Result类为统一的返回类
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.io.UnsupportedEncodingException;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends BaseGlobalExceptionHandler {
@ExceptionHandler(value = {BaseException.class})
@ResponseBody
public Result handler(BaseException exception){
// 根据BaseException默认的日志级别记ERROR日志
log("Job Service Error BaseException:", exception);
return Result.fail(exception.getErrorEnum());
}
@ExceptionHandler(value = {Exception.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Result handler(Exception exception){
// 可以指定这次的日志级别
log("Job Service Error Exception:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
@ExceptionHandler(value = {JobException.class})
@ResponseBody
public Result handler(JobException exception){
// JobException有覆写了父类的errorLevel,该日志级别为JobException中的errorLevel
log("Job Service Error JobException:", exception);
return Result.fail(exception.getErrorEnum());
}
@ExceptionHandler(value = {IllegalAccessException.class})
@ResponseBody
public Result handler(IllegalAccessException exception){
// 选你喜欢的,log.error或者BaseGlobalExceptionHandler.log,后者可以省引入@Slf4j
// log.error("Job Service Error IllegalAccessException:", exception);
log("Job Service Error IllegalAccessException:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
@ExceptionHandler(value = {UnsupportedEncodingException.class})
@ResponseBody
public Result handler(UnsupportedEncodingException exception){
// 选你喜欢的,log.error或者BaseGlobalExceptionHandler.log,后者可以省引入@Slf4j
log("Job Service Error UnsupportedEncodingException:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
}
基本错误类
import lombok.Getter;
@Getter
public class BaseException extends RuntimeException{
private final ErrorEnum errorEnum;
// 默认错误日志级别为ERROR
protected ExceptionLogLevelEnum errorLevel = ExceptionLogLevelEnum.ERROR;
private Object resultBody = null;
/**
* 异常时需要返回的resultBody
* @param result
* @return void
* @author linton.cao
* @date 2021/9/8 16:29
*/
public void setResultBody(Object result){
this.resultBody = result;
}
/**
* 默认错误
* @param
* @return
* @author linton.cao
* @date 2021/9/2 13:33
*/
public BaseException() {
super(CommonErrorEnum.SYSTEM_ERROR.getCodeStr());
this.errorEnum = CommonErrorEnum.SYSTEM_ERROR;
}
public BaseException (ErrorEnum errorEnum) {
super(errorEnum.getCodeStr());
this.errorEnum = errorEnum;
}
/**
* @param errorEnum
* @param logMessage 日志记录内容
* @return
* @author linton.cao
* @date 2021/9/2 13:56
*/
public BaseException (ErrorEnum errorEnum, String logMessage) {
super(errorEnum.getCodeStr() + " - " + logMessage);
this.errorEnum = errorEnum;
}
public BaseException(ErrorEnum errorEnum, String logMessage, Throwable cause) {
super(errorEnum.getCodeStr() + " - " + logMessage, cause);
this.errorEnum = errorEnum;
}
}
继承基本错误类,调用父类方法实现错误处理
import com.job51.dev.job.enums.JobErrorEnum;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Getter
@Slf4j
public class JobException extends BaseException {
//异常日志级别
private final ExceptionLogLevelEnum errorLevel = ExceptionLogLevelEnum.ERROR;
public JobException() {
super();
}
public JobException(JobErrorEnum jobErrorEnum){
super(jobErrorEnum);
}
public JobException(JobErrorEnum jobErrorEnum, String logMessage){
super(jobErrorEnum, logMessage);
}
public JobException(JobErrorEnum jobErrorEnum,String logMessage, Throwable cause){
super(jobErrorEnum,logMessage,cause);
}
}
业务中实际使用
try {
delete = stringRedisTemplate.delete(keyName);
}catch (Exception e){
throw new JobException(JobErrorEnum.SYSTEM_ERROR , MessageFormat.format("删除key发生错误,key:{0},e:{1}",keyName,e));
}
2022.8.19更新
报错有许多的场景应用,为了应对这些复杂的场景,需要新的处理错误方式
基本的错误处理类,用于在出错时增加日志
import java.text.MessageFormat;
@Getter
public class BaseException extends RuntimeException{
private static final long serialVersionUID = 1L;
private final ErrorEnum errorEnum;
/**
* 默认错误日志级别为ERROR
*/
protected ExceptionLogLevelEnum errorLevel = ExceptionLogLevelEnum.ERROR;
private Object resultBody = null;
/**
* 异常时需要返回的resultBody
* @param result 返回的body
*/
public void setResultBody(Object result){
this.resultBody = result;
}
/**
* 默认错误
*/
public BaseException() {
super(CommonErrorEnum.SYSTEM_ERROR.getCodeStr());
this.errorEnum = CommonErrorEnum.SYSTEM_ERROR;
}
public BaseException (ErrorEnum errorEnum) {
super(errorEnum.getCodeStr());
this.errorEnum = errorEnum;
}
public BaseException (ErrorEnum errorEnum, String logMessage) {
super(errorEnum.getCodeStr() + " - " + logMessage);
this.errorEnum = errorEnum;
}
public BaseException(ErrorEnum errorEnum, String logMessage, Throwable cause) {
super(errorEnum.getCodeStr() + " - " + logMessage, cause);
this.errorEnum = errorEnum;
}
public BaseException(ErrorEnum errorEnum, String logMessage, Object... messageParams) {
super(errorEnum.getCodeStr() + " - " + formatLogMessage(logMessage, messageParams));
this.errorEnum = errorEnum;
}
private static String formatLogMessage(String logMessage, Object... messageParams){
String logStr = logMessage;
if (messageParams.length>0) {
logStr = MessageFormat.format(logMessage, messageParams);
}
return logStr;
}
}
有些报错,是实际业务中会出现的报错,不需要记录error日志,则使用业务错误类抛出
package com.job51.dev.common.exception;
import com.job51.dev.common.enums.ErrorEnum;
import com.job51.dev.common.enums.ExceptionLogLevelEnum;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
*
*/
@Getter
@Slf4j
public class BaseBusinessException extends BaseException {
private static final long serialVersionUID = 1L;
private final ExceptionLogLevelEnum errorLevel = ExceptionLogLevelEnum.INFO;
public BaseBusinessException() {
super();
}
public BaseBusinessException(ErrorEnum errorEnum){
super(errorEnum);
}
public BaseBusinessException(ErrorEnum errorEnum, String logMessage){
super(errorEnum, logMessage);
}
public BaseBusinessException(ErrorEnum errorEnum, String logMessage, Throwable cause){
super(errorEnum,logMessage,cause);
}
public BaseBusinessException(ErrorEnum errorEnum, String logMessage, Object... messageParams) {
super(errorEnum, logMessage, messageParams);
}
}
捕捉不同的失败实例,比如FeignException,特殊场景下,失败了,也要返回实体
package com.job51.dev.common.exception;
import com.job51.dev.common.entity.dto.Result;
import com.job51.dev.common.enums.CommonErrorEnum;
import com.job51.dev.common.enums.ExceptionLogLevelEnum;
import com.job51.dev.common.utils.JacksonUtils;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.validation.ConstraintViolationException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
/**
* @author linton.cao
*/
@ControllerAdvice
@Slf4j
public class BaseGlobalExceptionHandler {
/**
* 继承BaseException的异常都可以自动按级别记错误日志
* @param s 日志内容
* @param ex 异常堆栈
* @author linton.cao
* @date 2021/9/4 20:35
*/
public static void log(String s, BaseException ex){
switch (ex.getErrorLevel()) {
case INFO:
log.info(s, ex);
break;
case WARN:
log.warn(s, ex);
break;
case ERROR:
default:
log.error(s, ex);
}
}
/**
* 其他类型异常需要指定日志级别
* @param s 日志内容
* @param ex 异常堆栈
* @param level 日志级别
* @author linton.cao
* @date 2021/9/4 20:35
*/
public static void log(String s, Throwable ex, ExceptionLogLevelEnum level){
switch (level) {
case INFO:
log.info(s, ex);
break;
case WARN:
log.warn(s, ex);
break;
case ERROR:
default:
log.error(s, ex);
}
}
@ExceptionHandler(value = {BaseException.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handler(BaseException exception){
// 根据BaseException默认的日志级别记ERROR日志
log("Service Exception:", exception);
return Result.fail(exception.getErrorEnum());
}
@ExceptionHandler(value = {BaseBusinessException.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public Result<?> handler(BaseBusinessException exception){
// 业务上的错误抓取,INFO级别
log("Service INFO BusinessException:", exception);
return Result.fail(exception.getErrorEnum());
}
@ExceptionHandler(value = {Exception.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handler(Exception exception){
// 可以指定这次的日志级别
log("Service Exception:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
/**
* 微服务响应失败处理
* @param exception FeignException
* @return 响应
*/
@ExceptionHandler(value = {FeignException.class})
@ResponseBody
public Result<?> handler(FeignException exception){
// feign调用失败
log("Service Feign Exception:", exception, ExceptionLogLevelEnum.ERROR);
int state = exception.status();
ServletRequestAttributes res = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (res != null && res.getResponse() != null){
res.getResponse().setStatus(state);
}
Result<?> preResult = JacksonUtils.jsonDecode(exception.contentUTF8(), Result.class);
if (preResult == null){
return Result.fail();
} else {
return Result.fail(preResult.getStatus(), preResult.getMessage(), preResult.getResultbody());
}
}
@ExceptionHandler(value = {IllegalAccessException.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handler(IllegalAccessException exception){
log("Service IllegalAccessException:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
@ExceptionHandler(value = {UnsupportedEncodingException.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handler(UnsupportedEncodingException exception){
log("Service UnsupportedEncodingException:", exception, ExceptionLogLevelEnum.ERROR);
return Result.fail();
}
@ResponseBody
@ExceptionHandler(BindException.class)
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public Result<?> handler(BindException exception) {
BindingResult result = exception.getBindingResult();
log("Service Valid Exception:", exception, ExceptionLogLevelEnum.INFO);
if (result.hasErrors()) {
Map<String,String> map = new HashMap<>(16);
result.getFieldErrors().forEach((item)->{
String message = item.getDefaultMessage();
// 获取错误的属性字段名
String field = item.getField();
map.put(field,message);
});
return Result.fail(CommonErrorEnum.PARAM_VALIDATE_ERROR, map);
}
return Result.fail(CommonErrorEnum.PARAM_VALIDATE_ERROR);
}
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public Result<?> handler(MethodArgumentNotValidException exception) {
BindingResult result = exception.getBindingResult();
log("Service Valid Exception:", exception, ExceptionLogLevelEnum.INFO);
if (result.hasErrors()) {
Map<String,String> map = new HashMap<>(16);
result.getFieldErrors().forEach((item)->{
String message = item.getDefaultMessage();
// 获取错误的属性字段名
String field = item.getField();
map.put(field,message);
});
return Result.fail(CommonErrorEnum.PARAM_VALIDATE_ERROR, map);
}
return Result.fail(CommonErrorEnum.PARAM_VALIDATE_ERROR);
}
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public Result<?> handler(ConstraintViolationException exception) {
log("Service Valid Exception:", exception, ExceptionLogLevelEnum.INFO);
return Result.fail(CommonErrorEnum.PARAM_VALIDATE_ERROR,exception.getMessage());
}
}
抛出错误时,需要返回对象,则定义一个特殊的Exception
public class CupidLoginDivulgeException extends BaseBusinessException {
private static final long serialVersionUID = 1L;
private final ExceptionLogLevelEnum errorLevel = ExceptionLogLevelEnum.INFO;
private final LoginInfoVO userInfo;
public CupidLoginDivulgeException(LoginInfoVO userInfo, ErrorEnum errorEnum, String logMessage, Object... messageParams) {
super(errorEnum, logMessage, messageParams);
this.userInfo = userInfo;
}
}
实际使用场景:
Result<?> preResult = JacksonUtils.jsonDecode(exception.contentUTF8(), Result.class);
//passport密码为泄露库密码,不可登录
if (preResult.getStatus().equals(PassportErrorEnum.LOGIN_DIVULGE_PASSWORD.getCodeStr())){
String logMessage = MessageFormat.format("调用passport账号密码登录,密码为泄露库密码,不可登录 请求参数:{0};passport返回:{1}", passwordLoginQuery, preResult);
LoginInfoVO loginInfoVO = new LoginInfoVO();
//需要返回app跳转链接
loginInfoVO.setUsername(getDivulgePasswordUrl((LoginUserDTO)preResult.getResultbody()));
throw new CupidLoginDivulgeException(loginInfoVO, CupidErrorEnum.LOGIN_FAILURE_COLLISION_LIBRARY, logMessage);
}
对应的handler中也需要捕捉这个错误
@ResponseBody
@ExceptionHandler(CupidLoginDivulgeException.class)
@ResponseStatus(code = HttpStatus.OK)
public Result<?> handler(CupidLoginDivulgeException exception) {
log("Service INFO PasswordLoginDivulgeException: ", exception);
return Result.fail(exception.getErrorEnum(), exception.getUserInfo());
}