• 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

  • 相关阅读:
    jQuery插件开发——元素拖拽和基于鼠标位置为中心缩放
    基于Quartz.Net的任务管理平台开发(3) —— 任务管理平台
    记录一下Linq实现多列排序的问题
    动态构造Lambda表达式
    MVC全局实现异常处理
    修改自增列起始值,仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'******'中的标识列指定显式值
    统计指定时间段内生日的用户
    SQL开关功能记录
    枚举对象实现 DropDownList 的转换操作二
    枚举对象实现 DropDownList 的转换操作一
  • 原文地址:https://www.cnblogs.com/Naylor/p/13594843.html
Copyright © 2020-2023  润新知