springboot logback日志+异常+阿里云日志 aliyun-log-logback-appender

  1. 前言

最近有个新项目用了,springboot3.0,以前项目日志保存得方式是阿里云云服务自动读取日志文件,最近项目部署得方式可能有变化,所以新项目用logback+aliyun-log-logback-appender得方式保存到阿里云日志服务。用logback得原因主要是懒,spring默认就是这个,其他还要各种配置和兼容。

重点

  1. 通过配置MDC控制保存到阿里云的数据,logback-spring.xml要配置对应的mdcFields

  1. 通过ContentCachingRequestWrapper和ContentCachingResponseWrapper取入参和返回数据,这两个不需要太多代码

  1. RestControllerAdvice+ExceptionHandler全局捕获异常并处理

  1. gradle

    implementation 'com.google.protobuf:protobuf-java:2.5.0'
    implementation 'com.aliyun.openservices:aliyun-log-logback-appender:0.1.18'
    implementation 'org.slf4j:slf4j-nop:1.7.25'

阿里云日志应该只需要这么多得引入。

  1. logback-spring.xml 日志配置



    
    

    
    
        
            
            [%d{yyyy-MM-dd HH:mm:ss.SSS}] %highlight([%-5level] [%thread] %logger{50} - %msg%n)
            UTF-8
        
    

    
    

    
        
        
        cn-beijing.log.aliyuncs.com
        ####
        ###

        
        #####
        
        
            ####dev
        
         
        
            ###test
        
        

        
        
        

        
        104857600
        0
        8
        524288
        4096
        2000
        10
        100
        50000

        






        
        yyyy-MM-dd'T'HH:mmZ
        
        UTC+8
        
            TraceId,#####
        
    
    
    
    
        
            
            
        
    
    
        
            
            
        
    

    
    
        
           。。。。。。
        
    

这是logback-spring.xml 基本配置。放在resources目录中。

###基本都是阿里云中得一些参数信息,熟悉阿里云日志的,都明白。

mdcFields也是我最近才发现,它会把MDC中的中对应不为null的字段保存在阿里云中。

  1. HttpFilter

因为在日志中要记录请求的参数和返回值,aop实现有点负责,所以用了HttpFilter

@Component
@WebFilter(filterName = "accessLogFilter", urlPatterns = "/*")
@Order(-9999)        // 保证最先执行
public class AccessLogFilter extends HttpFilter {

    private static final Logger logger = LoggerFactory.getLogger(AccessLogAspect.class);

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper cachingRequestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper cachingResponseWrapper = new ContentCachingResponseWrapper(response);

        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
        // 接收到请求,记录请求内容````
        MDC.put("TraceId", TraceIDUtils.getTraceId());
        //客户端类型
        MDC.put("ClientType", userAgent.getOperatingSystem().getDeviceType().getName());
        //客户端操作系统类型
        MDC.put("OsType", userAgent.getOperatingSystem().getName());
        MDC.put("ClientMethod", request.getMethod());
        MDC.put("ClientIp", IpUtil.getIpAddress(request));
        MDC.put("ClientParam", request.getQueryString());
        MDC.put("ClientUrl", request.getRequestURL().toString());
        MDC.put("ClientUri", request.getRequestURI());
        //记录请求开始时间
        long start = RequestTimeUtil.getStart();
        MDC.put("RequestStart", String.valueOf(start));
        //请求头参数
        Map headerMap = new HashMap<>();
        Enumeration enumeration = cachingRequestWrapper.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            headerMap.put(name, value);
        }
        MDC.put("RequestHeader", JSON.toJSONString(headerMap));
        MDC.put("LogType", LogTypEnum.requestStart.name());
        MDC.put("LogTime", String.valueOf(System.currentTimeMillis()));
        //日志输出
        logger.info("--------------request start--------------");
        //这里清理掉MDC 防止变成下一个log输出的垃圾数据
        MDC.remove("LogTime");
        MDC.remove("ClientType");
        MDC.remove("OsType");
        MDC.remove("ClientMethod");
        MDC.remove("ClientIp");
        MDC.remove("ClientParam");
        MDC.remove("ClientUrl");
        MDC.remove("ClientUri");
        MDC.remove("RequestStart");
        MDC.remove("LogType");
        MDC.remove("RequestHeader");
        super.doFilter(cachingRequestWrapper, cachingResponseWrapper, chain);
        String responseBody = new String(cachingResponseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);

        MDC.put("LogType", LogTypEnum.requestEnd.name());
        MDC.put("RequestParamMap", JSON.toJSONString(cachingRequestWrapper.getParameterMap()));
        String requestBodyParam = new String(cachingRequestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        MDC.put("RequestBodyParam", requestBodyParam);
        MDC.put("ReturnData", responseBody);
        long end = RequestTimeUtil.getEnd();
        MDC.put("RequestEnd", String.valueOf(end));
        MDC.put("RequestProcessTime", String.valueOf(end - start));
        MDC.put("LogTime", String.valueOf(System.currentTimeMillis()));
        logger.info("--------------request end--------------");
        // 这一步很重要,把缓存的响应内容,输出到客户端
        cachingResponseWrapper.copyBodyToResponse();
        MDC.clear();
        RequestTimeUtil.remove();
        TraceIDUtils.remove();
    }

找了很久才找到ContentCachingRequestWrapper和ContentCachingResponseWrapper这两个可以取入参和返回数据的类,不用动其他代码就可以实现了。

其实到这里最基础的请求日志,已经实现了。

springboot logback日志+异常+阿里云日志 aliyun-log-logback-appender_第1张图片

大概就是这样子的。

  1. 其他aop的日志也是差不多的方式去处理。

  1. 异常处理

RestControllerAdvice+ExceptionHandler全局捕获异常,ResultError是自定义异常,主要处理一些assert抛出的错误。


@RestControllerAdvice
public class ExceptionControllerAdvice {

    private static final Logger logger = LoggerFactory.getLogger(AccessLogAspect.class);

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return Result.fail(GlobalResultEnum.fail.getCode(), objectError.getDefaultMessage());
    }


    @ExceptionHandler(ResultError.class)
    public Result APIExceptionHandler(ResultError e) {
        MDC.put("ResponseStatus", String.valueOf(e.getResultEnum().getCode()));
        MDC.put("ResponseGlobalStatus", String.valueOf(e.getResultEnum().getGlobalStatus()));
        MDC.put("ResponseMsg", e.getResultEnum().getMsg());
        MDC.put("LogType", LogTypEnum.resultError.name());
        MDC.put("LogTime", String.valueOf(System.currentTimeMillis()));
        logger.info(JSON.toJSONString(e.getErrorMessage()));
        MDC.remove("LogTime");
        MDC.remove("ResponseStatus");
        MDC.remove("ResponseGlobalStatus");
        MDC.remove("ResponseMsg");
        MDC.remove("LogType");
        return Result.of(e);
    }

} 
   
public class ResultError extends RuntimeException {

    @Getter
    private ResultEnum resultEnum;
    @Getter
    private Object errorMessage;

    public ResultError(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.resultEnum = resultEnum;
    }

    public ResultError(ResultEnum resultEnum, Object errorMessage) {
        super(resultEnum.getMsg());
        this.resultEnum = resultEnum;
        this.errorMessage = errorMessage;
    }

}
public interface ResultEnum {

    /**
     * 状态码
     * @return int
     */
    int getCode();

    /**
     * 全局状态码
     * @return int
     */
    int getGlobalStatus();

    /**
     * 描述信息
     * @return String
     */
    String getMsg();

}

ResultEnum 是全局状态接口,所有的状态都可以继承这个类,实现类似这样。

public enum GlobalResultEnum implements ResultEnum {
    success(100, "success"),
    fail(99, "fail");

    private final int code;
    private final int globalStatus = 0;
    private final String msg;

    GlobalResultEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }


    /**
     * 状态码
     *
     * @return int
     */
    @Override
    public int getCode() {
        return code;
    }

    /**
     * 全局状态码
     *
     * @return int
     */
    @Override
    public int getGlobalStatus() {
        return globalStatus + code;
    }

    /**
     * 描述信息
     *
     * @return String
     */
    @Override
    public String getMsg() {
        return msg;
    }
}

全局定义唯一状态码(globalStatus),定义全局状态接口(ResultEnum)

也挺麻烦,还需要优化。

写的不是很清晰,欢迎讨论。

你可能感兴趣的:(springboot,日常记录,服务崛起之路,阿里云,云计算,springboot,java)