原文地址 https://juejin.im/post/5e073980f265da33f8653f2e

参考链接: SpringBoot 之全局异常处理_

推荐博客:glmapper 的 logback 博客logback-spring.xml 配置文件

代码地址:github

项目构建基础 - 统一结果,统一异常,统一日志

统一结果返回

目前的前后端开发大部分数据的传输格式都是 json,因此定义一个统一规范的数据格式有利于前后端的交互与 UI 的展示。

统一结果的一般形式

  1. 是否响应成功;
  2. 响应状态码;
  3. 状态码描述;
  4. 响应数据
  5. 其他标识符

结果类枚举

  • 前三者可定义结果枚举,如:success,code,message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(true,5000,"成功"),
FAIL(false,5001,"失败"),
UNKNOWN_ERROR(false,5002,"未知错误"),
PARAM_ERROR(false,5003,"参数错误") ,
NULL_POINTER(false,5004,"空指针异常")
;

// 响应是否成功
private Boolean success;
// 响应状态码
private Integer code;
// 响应信息
private String message;

ResultCodeEnum(boolean success, Integer code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
}

统一结果类

  • 第 5 个属于自定义返回,利用前 4 者可定义统一返回对象

注意:

  1. 外接只可以调用统一返回类的方法,不可以直接创建,影刺构造器私有;
  2. 内置静态方法,返回对象;
  3. 为便于自定义统一结果的信息,建议使用链式编程,将返回对象设类本身,即 return this;
  4. 响应数据由于为 json 格式,可定义为 JsonObject 或 Map 形式;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public class ResultResponse {
private Boolean success;

private Integer code;

private String message;

private Map<String, Object> data = new HashMap<>();

/**
* 构造器私有
*/
private ResultResponse(){}

/**
* 通用返回成功
* @return
*/
public static ResultResponse ok() {
ResultResponse resultResponse = new ResultResponse();
resultResponse.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
resultResponse.setCode(ResultCodeEnum.SUCCESS.getCode());
resultResponse.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return resultResponse;
}

/**
* 通用返回失败,未知错误
* @return
*/
public static ResultResponse error() {
ResultResponse resultResponse = new ResultResponse();
resultResponse.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());
resultResponse.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());
resultResponse.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());
return resultResponse;
}

/**
* 设置结果,形参为结果枚举
* @param result
* @return
*/
public static ResultResponse setResult(ResultCodeEnum result) {
ResultResponse resultResponse = new ResultResponse();
resultResponse.setSuccess(result.getSuccess());
resultResponse.setCode(result.getCode());
resultResponse.setMessage(result.getMessage());
return resultResponse;
}

/**
* 自定义返回数据
* @param map
* @return
*/
public ResultResponse data(Map<String,Object> map) {
this.setData(map);
return this;
}

/**
* 通用设置data
* @param key
* @param value
* @return
*/
public ResultResponse data(String key, Object value) {
this.data.put(key, value);
return this;
}

/**
* 自定义状态信息
* @param message
* @return
*/
public ResultResponse message(String message) {
this.setMessage(message);
return this;
}

/**
* 自定义状态码
* @param code
* @return
*/
public ResultResponse code(Integer code) {
this.setCode(code);
return this;
}

/**
* 自定义返回结果
* @param success
* @return
*/
public ResultResponse success(Boolean success) {
this.setSuccess(success);
return this;
}
}

控制层返回

  • 视图层使用统一结果
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/api/v1/users")
public class TeacherAdminController {

@Autowired
private UserService userService;

@GetMapping
public ResultResponse list() {
List<Teacher> list = teacherService.list(null);
return ResultResponse.ok().data("itms", list).message("用户列表");
}
}
  • json 结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"success": true,
"code": 5000,
"message": "查询用户列表",
"data": {
"itms": [
{
"id": "1",
"username": "admin",
"role": "ADMIN",
"deleted": false,
"gmtCreate": "2019-12-26T15:32:29",
"gmtModified": "2019-12-26T15:41:40"
},{
"id": "2",
"username": "zhangsan",
"role": "USER",
"deleted": false,
"gmtCreate": "2019-12-26T15:32:29",
"gmtModified": "2019-12-26T15:41:40"
}
]
}
}

统一结果类的使用参考了 mybatis-plus 中 R 对象的设计

统一异常处理

使用统一返回结果时,还有一种情况,就是程序的保存是由于运行时异常导致的结果,有些异常我们可以无法提前预知,不能正常走到我们 return 的 ResultResponse 对象返回。

因此,我们需要定义一个统一的全局异常来捕获这些信息,并作为一种结果返回控制层

@ControllerAdvice

该注解为统一异常处理的核心

是一种作用于控制层的切面通知(Advice),该注解能够将通用的 @ExceptionHandler、@InitBinder 和 @ModelAttributes 方法收集到一个类型,并应用到所有控制器上

该类中的设计思路:

  1. 使用 @ExceptionHandler 注解捕获指定或自定义的异常;
  2. 使用 @ControllerAdvice 集成 @ExceptionHandler 的方法到一个类中;
  3. 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
  4. 自定一个异常类,捕获针对项目或业务的异常;
  5. 异常的对象信息补充到统一结果枚举中;

自定义全局异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com._4paradigm.data.analysis.common.response.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;

public class CustomException extends RuntimeException{

@Autowired
ResultCodeEnum resultCode;

// 带参构造
public CustomException(ResultCodeEnum resultCode){
this.resultCode = resultCode;
}

public ResultCodeEnum getResultCode(){
return resultCode;
}

/**
* 静态方法
* 用法:CustomException.cast(ResultCodeEnum.FAIL);
* @param resultCode
*/
public static void cast(ResultCodeEnum resultCode){
throw new CustomException(resultCode);
}
}

统一异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com._4paradigm.data.analysis.common.response.ResultCodeEnum;
import com._4paradigm.data.analysis.common.response.ResultResponse;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 定义map,存贮常见错误信息。该类map不可修改
*/
private static ImmutableMap<Class<? extends Throwable>,ResultCodeEnum> EXCEPTIONS;
/**
* 构建ImmutableMap
*/
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCodeEnum> builder = ImmutableMap.builder();

/**
* 捕获CustomException类异常
* @param customException
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResultResponse customException(CustomException customException){
ResultCodeEnum resultCode = customException.getResultCode();
return ResultResponse.setResult(resultCode);
}

/**
* 空指针异常处理方法
* @param e
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResultResponse error(NullPointerException e) {
log.error(ExceptionUtil.getMessage(e));
return ResultResponse.setResult(ResultCodeEnum.NULL_POINTER);
}

/**
* 捕获非自定义类异常
* @param exception
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultResponse exception(Exception exception){
// 记录日志
log.error(ExceptionUtil.getMessage(exception));
if (EXCEPTIONS == null){
EXCEPTIONS = builder.build();
}
ResultCodeEnum resultCode = EXCEPTIONS.get(exception.getClass());
if (resultCode != null){
return ResultResponse.setResult(resultCode);
}else {
return ResultResponse.setResult(ResultCodeEnum.UNKNOWN_ERROR);
}
}

static {
builder.put(HttpMessageNotReadableException.class, ResultCodeEnum.PARAM_ERROR);
}
}

控制层展示

以下为展示当遇到 null 指定异常时,返回的结果信息

1
2
3
4
5
6
{
"success": false,
"code": 5004,
"message": "空指针异常",
"data": {}
}

本节介绍统一异常较为简略,推荐博客 SpringBoot 之全局异常处理

统一日志收集

日志是追踪错误定位问题的关键,尤其在生产环境中,需要及时修复热部署,不会提供开发者 debug 的环境,此时日志将会是最快解决问题的关键

日志的框架比较丰富,由于 spring boot 对 logback 的集成,因此推荐使用 logback 在项目中使用。

Logback

关于 logback 的配置和介绍,可以参考官网或推荐博客 glmapper 的 logback 博客logback-spring.xml 配置文件

配置

以下直接贴出配置信息,介绍信息科直接参考备注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<?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="10 seconds">
<contextName>logback</contextName>

<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
<property />

<!--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 ${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %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 >
<!--此日志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>

<!--2. 输出到文档-->
<!-- 2.1 level为 DEBUG 日志,时间滚动输出 -->
<appender >
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/edu_debug.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}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 2.2 level为 INFO 日志,时间滚动输出 -->
<appender >
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/edu_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}/web-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>

<!-- 2.3 level为 WARN 日志,时间滚动输出 -->
<appender >
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/edu_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}/web-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>

<!-- 2.4 level为 ERROR 日志,时间滚动输出 -->
<appender >
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/edu_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}/web-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,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
<logger />
<logger />
-->

<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
【logging.level.org.mybatis=debug logging.level.dao=debug】
-->

<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->

<!-- 4. 最终的策略 -->
<!-- 4.1 开发环境:打印控制台-->
<springProfile >
<logger />
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</springProfile>


<!-- 4.2 生产环境:输出到文档-->
<springProfile >
<logger />
<root level="info">
<appender-ref ref="ERROR_FILE" />
<appender-ref ref="WARN_FILE" />
</root>
</springProfile>

</configuration>

日志收集异常信息

日志信息往往伴随着异常信息的输出,因此,我们需要修改统一异常的处理器,将异常信息以流的方式写到日志文件中

  • 异常信息文件工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

@Slf4j
public class ExceptionUtil {
/**
* 打印异常信息
*/
public static String getMessage(Exception e) {
String swStr = null;
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
e.printStackTrace(pw);
pw.flush();
sw.flush();
swStr = sw.toString();
} catch (IOException ex) {
ex.printStackTrace();
log.error(ex.getMessage());
}
return swStr;
}
}
  • 修改统一异常处理器,将异常方法中的直接打印改为日志输入并打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com._4paradigm.data.analysis.common.response.ResultCodeEnum;
import com._4paradigm.data.analysis.common.response.ResultResponse;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 定义map,存贮常见错误信息。该类map不可修改
*/
private static ImmutableMap<Class<? extends Throwable>,ResultCodeEnum> EXCEPTIONS;
/**
* 构建ImmutableMap
*/
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCodeEnum> builder = ImmutableMap.builder();

/**
* 捕获CustomException类异常
* @param customException
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResultResponse customException(CustomException customException){
ResultCodeEnum resultCode = customException.getResultCode();
return ResultResponse.setResult(resultCode);
}

/**
* 空指针异常处理方法
* @param e
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResultResponse error(NullPointerException e) {
log.error(ExceptionUtil.getMessage(e));
return ResultResponse.setResult(ResultCodeEnum.NULL_POINTER);
}

/**
* 捕获非自定义类异常
* @param exception
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultResponse exception(Exception exception){
// 记录日志
log.error(ExceptionUtil.getMessage(exception));
if (EXCEPTIONS == null){
EXCEPTIONS = builder.build();
}
ResultCodeEnum resultCode = EXCEPTIONS.get(exception.getClass());
if (resultCode != null){
return ResultResponse.setResult(resultCode);
}else {
return ResultResponse.setResult(ResultCodeEnum.UNKNOWN_ERROR);
}
}

static {
builder.put(HttpMessageNotReadableException.class, ResultCodeEnum.PARAM_ERROR);
}
}

注意

  1. 日志的环境即 spring.profiles.acticve,跟随项目启动;
  2. 启动后,即可到自定目录查找到生成的日志文件;
  3. 本地 idea 调试时,推荐 Grep Console 插件可实现控制台的自定义颜色输出

详细过程,可参考源代码:github.com/chetwhy/clo…