提下比较好点
包含将捕获的异常堆栈完整的返回给前端。方便 后端人员用 swagger 或 knife 工具验证接口时,直接看到异常。
有啥用呢?在现场环境,或不方便远程服务器机器时,非常有用!!!
同时,文件日志太有用了!!! 尤其在无法查看 控制台时,简直就是救命稻草!!!
(无法看控制台,却能看到日志文件的情况 ,在 Azure 云 服务非常常见。)
我用的idea,springboot 2.7.15,不需要额外引入 库。
加入日志
application.yml 内需要加入log配置:
logging:
config: classpath:logback-spring.xml
file:
name: logs\app.log
level:
# 全局日志级别,可选TRACE, DEBUG, INFO, WARN, ERROR
# 优先级大于logback-spring.xml中<root>标签下的配置
root: INFO
上面写了 logback-spring.xml 的配置文件是放在 classpath 下,所以我们开发时,放在和 application.yaml 同级目录即可。
贴上通用的 logback-spring.xml 模板:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天滚动 -->
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留 7 天的日志 -->
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
在使用上,controller,service,dao,这些常用bean 的 class头部引入标签:
@Slf4j 即可
之后 在bean 内部就能直接用了:
log.info("xxx");
log.error("xxx");
controller 加入全局异常捕获
对于那些和业务无关的异常,controller 再也不用对乱七八糟的异常各种捕获了,service,dao 也可以直接往外抛异常了。
这是多么开心的事!!!
全部异常捕获类:
package com.example.demo.config;
import com.example.demo.utils.LogExceptionStackUtil;
import com.example.demo.utils.Result;
import com.example.demo.utils.SCSRestfulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.RestClientException;
import java.util.logging.Logger;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 专门处理 访问 restul 接口时的异常
* @param e
* @return
*/
@ExceptionHandler(RestClientException.class)
public Result<String> handleException(SCSRestfulException e)
{
StackTraceElement stackTrace = e.getStackTrace()[0];
String methodName = stackTrace.getMethodName();
String fileName = stackTrace.getFileName();
String className = stackTrace.getClassName();
int lineNumber = stackTrace.getLineNumber();
log.error("异常发生在文件{}的类{}中的方法{}的第{}行',异常信息:{}", fileName,className,methodName,lineNumber,e.getMessage());
String errMsg = LogExceptionStackUtil.logExceptionStack(e);
log.error("====>"+errMsg);
return Result.Label.ERROR_DataSourceServerAccessIsNotResponded.as(errMsg);
}
/**
* 捕获基类Throwable
* HttpStatus.INTERNAL_SERVER_ERROR 对应 code=500
* @param e
* @return
*/
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<String> handler(Throwable e) {
String errMsg = LogExceptionStackUtil.logExceptionStack(e);
log.error("====>"+errMsg);
return Result.Label.ERROR_ServerIsError.as(errMsg);
}
}
Result 类
package com.example.demo.utils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @author Rain
* @date 2023/11/30
*/
@ApiModel(description= "响应数据结构")
@Data
public class Result<T> implements Serializable {
@ApiModelProperty(value = "响应代码{<br />" +
"Success(0),<br />" +
"<br />" +
" Failure(-1),<br />" +
"<br />" +
" FAILED_InvalidParameter(-2),<br />" +
"<br />" +
" FAILED_UnsupportedDataSourceType(-3),<br />" +
"<br />" +
" FAILED_UnauthorizedCodeOfDataLake(-4),<br />" +
"<br />" +
" FAILED_NoDataMatchConditions(-5),<br />" +
"<br />" +
" ERROR_DataSourceServerAccessIsNotResponded(-6),<br />" +
"<br />" +
" ERROR_ServerIsError(-7);<br />" +
"<br />" +
" ERROR_InvalidToken(-8);<br />" +
"<br />" +
" ERROR_TokenIsExpirated(-9);<br />" +
"}")
private Integer code;
@ApiModelProperty(value = "响应信息")
private String msg;
@ApiModelProperty(value = "数据")
private T data;
private Result(){
}
private static <T> Result<T> build(Label label) {
return build(label, null);
}
private static <T> Result<T> build(Label label, T result) {
return build(label, label.name(), result);
}
private static <T> Result<T> build(Label label, String message, T result) {
Result<T> resultJson = new Result<>();
resultJson.code = label.code;
resultJson.msg = message;
resultJson.data = result;
return resultJson;
}
public static enum Label {
/**
* code 遵守如下约定:
* 正数 代表 期望现象;
* 负值 代表悲观现象;
* 0 代表 基本符合预期。
*/
Success(0),
/**
* 虽然异常,但无法提供更多额外信息
*/
Failure(-1),
/**
* 无效参数
* 不符合服务端基本校验要求
*/
FAILED_InvalidParameter(-2),
/**
* 数据源类型不支持
*/
FAILED_UnsupportedDataSourceType(-3),
/**
* 您的 code 未经授权,无法提供token
*/
FAILED_UnauthorizedCodeOfDataLake(-4),
/**
* 您的没有数据能够匹配您的条件查询
*/
FAILED_NoDataMatchConditions(-5),
/**
* 数据源服务器访问异常
*/
ERROR_DataSourceServerAccessIsNotResponded(-6),
/**
* 零件查询服务异常
*/
ERROR_ServerIsError(-7),
/**
* 无效 token
*/
ERROR_InvalidToken(-8),
/**
* token 过期
*/
ERROR_TokenIsExpirated(-9);
private Integer code;
Label(Integer code) {
this.code = code;
}
public <T> Result<T> as(){
return Result.build(this);
}
public <T> Result<T> as(T data){
return Result.build(this,data);
}
}
}
上面有 swagger 的标签,这里的对它的配置,就不赘述了。如果觉得懒,那些标签删了也行。 LogExceptionStackUtil 类
package com.example.demo.utils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author Rain
* 将 异常的堆栈信息输出的 外部的 String 流中
*/
public class LogExceptionStackUtil {
public static String logExceptionStack(Throwable e) {
StringWriter errorsWriter = new StringWriter();
e.printStackTrace(new PrintWriter(errorsWriter));
return errorsWriter.toString();
}
}
如标题所说的好处,将
e.printStackTrace()
内容直接打印到日志出来,并且也能将堆栈内容,通过 Result 返回给前端(比如 swagger) ,是多么快乐的事。
贴上 swagger 调用时发生异常时的效果: