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
Spring Boot日志框架Slf4j+logback的更多相关文章
- Spring Boot 日志记录 SLF4J
Spring Boot 日志记录 SLF4J 2016年01月12日 09:25:28 阅读数:54086 在开发中打印内容,使用 System.out.println() 和 Log4j 应当是人人 ...
- 54. spring boot日志升级篇—logback【从零开始学Spring Boot】
在<44. Spring Boot日志记录SLF4J>章节中有关相关的介绍,这里我们在深入的了解下logback框架. 为什么要使用logback ? --在开发中不建议使用System. ...
- Springboot 系列(四)Spring Boot 日志框架
注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 Spring 框架选择使用了 JCL 作为默 ...
- 十四、Spring Boot 日志记录 SLF4J
在开发中打印内容,使用 System.out.println() 和 Log4j 应当是人人皆知的方法了. 其实在开发中我们不建议使用 System.out 因为大量的使用 System.out 会增 ...
- spring boot 日志介绍 以及 logback配置示例
https://www.cnblogs.com/flying607/p/7827460.html 以下是springboot的一个局部依赖关系: 可以看到,java util logging(jul) ...
- 【串线篇】spring boot日志框架
一.日志框架 小张:开发一个大型系统: 1.System.out.println(""):将关键数据打印在控制台:去掉?写在一个文件? 2.框架来记录系统的一些运行时信息:日志框架 ...
- (44). Spring Boot日志记录SLF4J【从零开始学Spring Boot】
在开发中打印内容,使用 System.out.println() 和 Log4j 应当是人人皆知的方法了. 其实在开发中我们不建议使用 System.out 因为大量的使用 System.out 会增 ...
- Spring Boot日志集成实战
Spring Boot日志框架 Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用 ...
- Spring Boot日志集成
Spring Boot日志框架 Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用 ...
- 50. Spring Boot日志升级篇—log4j【从零开始学Spring Boot】
如果你使用的是spring boot 1.4.0版本的话,那么你可能需要配合以下文章进行学习 90.Spring Boot 1.4 使用log4j错误[从零开始学Spring Boot] Log4j是 ...
随机推荐
- DarkMode(2):深色模式解决方案——css颜色变量实现Dark Mode
暗黑模式实现,最初的设计,就是参考之前的主题模式.所谓多套主题/配色/皮肤,就是我们很常见的换肤功能.换肤简单的实现就是更换 css实现不同样式呈现不同肤色. 之前做不同颜色的皮肤,暗黑模式可以单做其 ...
- 企业需要知道的5个 IAM 最佳实践
在之前的文章中,我们了解了在代码发布到 GitHub 之前如何管理用户权限.但你知道吗?人为错误竟然是迄今为止数据泄露的主要原因!根据统计,高达95%的数据泄露是由配置错误和不良网络环境引起的.黑客通 ...
- 十问ByteHouse:如何基于ClickHouse玩转向量检索?
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 向量检索被广泛使用于以图搜图.内容推荐以及大模型推理等场景.随着业务升级与 AI 技术的广泛使用,用户期望处理的向 ...
- 火山引擎云原生数据仓库 ByteHouse 技术白皮书 V1.0 (Ⅴ)
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 近日,<火山引擎云原生数据仓库 ByteHouse 技术白皮书>正式发布.白皮书简述了 ByteHou ...
- Solon 框架,想要使用 http2 怎么办?
要使用支持 http2 的插件:solon.boot.undertow (目前,只有它支持),然后通过代码启用: @SolonMain public class SeverDemo { public ...
- Jenkins + SVN/Git + Maven + Docker + 阿里云镜像 + Kubernetes(K8S)
Jenkins 2361.2 + Maven Integration + SVN/GIT + Docker + 阿里云镜像 + Kubernetes(K8S) 本文用于学习,了解原理,和实际应用,有所 ...
- python版本升级到3.8以及安装虚拟环境
linux系统升级python版本看起来复杂,如果知道其中步骤也不会觉得很困难.下面记录在deepin系统 15.11和ubuntu16.04系统下将python从2.7升级到3.8过程 升级pyth ...
- Android 黑马 52 期视频,不加密,免费下载
Android 黑马学习视频 目录 01.安卓基础+JNI (14天) 02.Android应用开 发-代码版本管理和实战(1天) 03.android案 例与项目_手机安全卫士(12天) 04.自定 ...
- Go--变量的声明
Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性. 变量是一段或多段用来存储数据的内存,在go中,变量一旦被定义,一定要使用,不然会报错 内建变量类型 ...
- k8s-修改线程数
1.背景: (1)胖容器ssh登录报错:handshake error (2)登录宿主机后,观察pod状态为running,但是kubectl exec 和docker exec 均无法进入该容器,报 ...