Spring Boot日志框架Slf4j+logback


一、简介

Slf4j

Java的简单日志记录外观(Simple Logging Facade for Java )可作为各种日志记录框架(例如java.util.logging,logback,log4j)的简单外观或抽象,允许终端用户在开发时插入所需的日志记录框架。简单来说,Slf4j定义的一种规范,java程序在记录日志时候的规范,这种规范是一个空壳,在实际开发中需要集成具体的日志框架来干活,这种具体的日志框架需要满足一些标准:符合Slf4j定义的标准;能够提供日志记录的功能。

Logback

一个“可靠、通用、快速而又灵活的Java日志框架”。logback是log4j的升级迭代产品,在许多地方相比于log4j有优势:

  • 1:性能,提升近10倍,初始内存减少了许多
  • 2:对Slf4j友好,同时引用这两个框架之后,甚至不需要额外的配置就可以很融洽的运行起来
  • 3:自动重新加载配置文件
  • 4:强大的研发团队和完善的文档

logback的三大核心模块:
 logback-classic:log4j的一个改良版本,同时整合了对Slf4j的支持
 logback-access:Servlet容器集成提供通过HTTP来访问日志的功能
 logback-core:其他两个模块的基础模块

二、Spring Boot集成

  • 1:pom中新增的dependency



    org.slf4j
    slf4j-api
    1.7.25





    ch.qos.logback
    logback-core
    1.2.3


    ch.qos.logback
    logback-classic
    1.2.3


    ch.qos.logback
    logback-access
    1.2.3


  • 2: 在resources下新建logback配置文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>

    
    
    
    
        
        
            ERROR
            
            DENY
            
            ACCEPT
        
        true
        false
        
            %d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            
        
        
            ${LOG_HOME}/leading-%d{yyyy-MM-dd}.%i.log
            256MB
            15
            32GB
        
    


    
        true
        false
        
            %d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            
        
        
            ${LOG_HOME}/leading-access-log-%d{yyyy-MM-dd}.%i.log
            256MB
            15
            32GB
        
    


    
        
            ERROR
        
        true
        false
        
            %d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            
        
        
            ${LOG_HOME}/leading-error-%d{yyyy-MM-dd}.%i.log
            256MB
            15
            32GB
        
    


    
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %5p %c.%M:%L - %m%n
        
    


    
        
        
        
    
    
        
    


  • 3:编写测试代码,并启动程序

  • 4:若设置应用logging级别为debug,可以看到日志也记录到了相应的文件中

  • 5:记录应用每一个service层中的方法的入参和出参

编写切面aspect


package com.naylor.debrisapp.logback.aspect;




import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.naylor.debrisapp.logback.utils.ObjectUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;


/**
 * @BelongsProject: debris-app
 * @BelongsPackage: com.naylor.debrisapp.logback.aspect
 * @Author: Chenml
 * @CreateTime: 2020-08-21 16:09
 * @Description: 日志
 */
@Aspect
@Component
public class LogAspect {


    private static final Logger logger = LoggerFactory.getLogger("AccessLog");


    private static final String[] ignoreMethods = new String[]{"login", "changePassword", "modifyPassword", "uploadFile", "downloadLatestFileByType", "syncPurchaseRequirement"};
    private static final String[] sensitiveWords = null; //new String[]{"password", "token", "base64"};


    // 大于100K的log不显示
    private static final Integer maxLimit = 1024 * 100;


    private static final Integer minLimit = 1024;


    //统一记录service层方法入参
    @Before("execution(* com.naylor..service..*.*(..))")
    public void doBefore(JoinPoint jp) {
        Signature signature = jp.getSignature();
        if (signature != null) {
            StringBuilder log = new StringBuilder("enter className:");
            String className = signature.getDeclaringTypeName();
            log.append(className);
            String methodName = signature.getName();
            log.append(",methodName:").append(methodName);
            if (!ignoreMethod(methodName)) {
                //region  排除含有敏感信息的参数输出逻辑
//                String argStr = JSON.toJSONString(jp.getArgs(), LogPropertyFilter.LOG_FILE_FILTER, SerializerFeature.WriteClassName);
//                // 排除含有敏感信息的参数输出
//                if (!containsSensitiveWords(argStr)) {
//                    log.append(",args:").append(argStr);
//                } else {
//                    log.append(",args:").append("sensitive word in args and forbidden to print.");
//                }
                //endregion
                String argStr = JSON.toJSONString(jp.getArgs(), SerializerFeature.WriteClassName);
                log.append(",args:").append(argStr);
            } else {
                log.append(",args:").append("ignore method and forbidden to print args.");
            }
            String logStr = log.toString();
            if (Modifier.isPublic(signature.getModifiers())) {
                logger.info("####### {}", logStr);
            } else {
                logger.debug("####### {}", logStr);
            }
        }
    }


    //统一记录service层方法出参
    @AfterReturning(value = "execution(* com.naylor..service..*.*(..))", returning = "returnValue")
    public void doAfterReturn(JoinPoint jp, Object returnValue) {
        Signature signature = jp.getSignature();
        if (signature != null) {
            StringBuilder log = new StringBuilder("leave className:");
            String className = signature.getDeclaringTypeName();
            log.append(className);
            String methodName = signature.getName();
            log.append(",methodName:").append(methodName);
            // 排除含有敏感信息的log输出
            if (!ignoreMethod(methodName)) {
                String argStr = ObjectUtil.toString(jp.getArgs());
                //region
//                if (!containsSensitiveWords(argStr)) {
//                    log.append(",args:").append(argStr);
//                } else {
//                    log.append(",args:").append("sensitive word in args and forbidden to print.");
//                }
                //endregion
            } else {
                log.append(",args:").append("ignore method and forbidden to print args.");
            }
            log.append(",return:");
            if (null != returnValue) {
                log.append(returnValue.getClass().getName() + ":");
                if (returnValue instanceof Collection) {
                    log.append("/size:").append(CollectionUtils.size(returnValue));
                } else if (returnValue instanceof Map) {
                    log.append("/size:").append(CollectionUtils.size(((Map) returnValue).entrySet()));
                } else {
                    String resStr = returnValue.toString();
                    log.append(resStr);
                    //region
//                    String printStr = null;
//                    if (!containsSensitiveWords(resStr)) {
//                        if (resStr.length() > maxLimit) {
//                            printStr = resStr.substring(0, minLimit);
//                        } else {
//                            printStr = resStr;
//                        }
//                        log.append(printStr);
//                    } else {
//                        log.append("sensitive word in response and forbidden to print.");
//                    }
                    //endregion
                }
            } else {
                log.append("");
            }
            String logStr = log.toString();
            if (Modifier.isPublic(signature.getModifiers())) {
                logger.info("####### {}", logStr);
            } else {
                logger.debug("####### {}", logStr);
            }
        }
    }


    //过滤不记录入参出参的方法
    private boolean ignoreMethod(String methodName) {
        boolean result = false;
        if (null != ignoreMethods && ignoreMethods.length > 0) {
            for (String checkMethod : ignoreMethods) {
                if (checkMethod.equalsIgnoreCase(methodName)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }


    //过滤掉含有敏感信息的方法
    private boolean containsSensitiveWords(String sensiWord) {
        boolean result = false;
        if (null != sensitiveWords && sensitiveWords.length > 0) {
            for (String checkWord : sensitiveWords) {
                if (sensiWord.contains(checkWord)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }
}

编写service


package com.naylor.debrisapp.logback.service;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;


/**
 * @BelongsProject: debris-app
 * @BelongsPackage: com.naylor.debrisapp.logback.service
 * @Author: Chenml
 * @CreateTime: 2020-08-21 17:22
 * @Description: 测试实现
 */
@Service
@Slf4j
public class TestImpl  implements  Test {


    @Override
    public String getHello(String id) {
        log.info("log.info.service");
        return "Hello , World!";
    }
}

在浏览器请求编写的service之后,入参和出参已经记录在了文件中

引用:

Logback.xml配置文件详解:https://www.jianshu.com/p/89bed7c7f1d7
https://www.jianshu.com/p/e3aeaf557f14

https://www.jianshu.com/p/b460f28153bb

https://www.jianshu.com/p/34cc56137c5a

logback简介和基本概念: