提下比较好点

包含将捕获的异常堆栈完整的返回给前端。方便 后端人员用 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 调用时发生异常时的效果:

spring boot 实现统一请求日志拦截_后端