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

<!--  slf4j   -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>


<!--  logback   -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-access</artifactId>
    <version>1.2.3</version>
</dependency>

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="encoding" value="UTF-8"/>
    <!--定义日志文件的存储地址 勿在LogBack的配置中使用相对路径-->
    <property name="LOG_HOME" value="/tmp/debris-app-logs"/>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 过滤日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <!-- 如果命中就禁止这条日志 -->
            <onMatch>DENY</onMatch>
            <!-- 如果没有命中就使用这条规则 -->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <Append>true</Append>
        <prudent>false</prudent>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/leading-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>256MB</maxFileSize>
            <maxHistory>15</maxHistory>
            <totalSizeCap>32GB</totalSizeCap>
        </rollingPolicy>
    </appender>


    <appender name="ACCESS_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Append>true</Append>
        <prudent>false</prudent>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/leading-access-log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>256MB</maxFileSize>
            <maxHistory>15</maxHistory>
            <totalSizeCap>32GB</totalSizeCap>
        </rollingPolicy>
    </appender>


    <appender name="LEADING_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <Append>true</Append>
        <prudent>false</prudent>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd/HH:mm:ss.SSS}|%X{localIp}|%X{requestId}|%X{requestSeq}|^_^|[%t] %-5level %logger{50}
                %line - %m%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/leading-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>256MB</maxFileSize>
            <maxHistory>15</maxHistory>
            <totalSizeCap>32GB</totalSizeCap>
        </rollingPolicy>
    </appender>


    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %5p %c.%M:%L - %m%n</pattern>
        </encoder>
    </appender>


    <root additivity="false" level="INFO">
        <appender-ref ref="FILE"/>
        <appender-ref ref="LEADING_ERROR"/>
        <appender-ref ref="STDOUT"/>
    </root>
    <logger name="AccessLog" additivity="false">
        <appender-ref ref="ACCESS_LOG"/>
    </logger>
</configuration>

  • 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简介和基本概念:https://www.cnblogs.com/yangyongjie/p/11146921.html

原文地址:https://www.cnblogs.com/Naylor/p/13594843.html