目录
1. 项目环境
- IDEA 2020.1.4
- Maven 3.6
- JDK 1.8
- SpringBoot 2.x
项目文件在GitHub(欢迎star⭐):
https://github.com/Gang-bb/Gangbb-SpringBoot
如有疑问或是建议,欢迎评论区留言或者QQ:949526365
2. 选型思考

Java 中比较常用的日志框架:
- log4j(
Log for Java
):Apache 的一个开源项目,七种日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE - logback:是一个很成熟的日志框架,其实 logBack 和 log4j 出自一个人之手,这个人就是 Ceki Gülcü。logback 比 log4j 大约快 10 倍、消耗更少的内存,迁移成本也很低,自动压缩日志、支持多样化配置、不需要重启就可以恢复 I/O 异常等优势
- log4j2:作者认为,log4j2已经不仅仅是 log4j 的一个升级版本了,而是从头到尾被重写的,这可以认为这其实就是完全不同的两个框架
Spring Boot 默认使用 logback,但相比较而言,log4j2 在性能上面会更好。SpringBoot 高版本都不再支持 log4j,而是支持 log4j2。log4j2,在使用方面与 log4j 基本上没什么区别,比较大的区别是 log4j2 不再支持 properties 配置文件,支持 xml、json 格式的文件。
Java 简易日志门面(Simple Logging Facade for Java
,缩写 SLF4J),它并不是真正的日志框架,他是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有 Java logging API、log4j 及 logback 等框架。
接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是 slf4j 的包而不是具体某个日志框架中的包),即直接和接口交互,不直接使用实现,所以可以任意的更换实现而不用更改代码中的日志相关代码。
比如:slf4j 定义了一套日志接口,项目中使用的日志框架是logback,开发中调用的所有接口都是 slf4j 的,不直接使用 logback,调用是 自己的工程调用 slf4j 的接口,slf4j 的接口去调用 logback 的实现,可以看到整个过程应用程序并没有直接使用 logback,当项目需要更换更加优秀的日志框架时(如log4j2)只需要引入 log4j2 的 jar 和 Llg4j2 对应的配置文件即可,完全不用更改 Java 代码中的日志相关的代码 logger.info(“xxx”)
,也不用修改日志相关的类的导入的包( import org.slf4j.Logger; import org.slf4j.LoggerFactory;
)
总结:使用日志接口便于更换为其他日志框架。
PS:以上为参考文章截取
阿里巴巴Java开发手册中对于日志的要求:

毕竟大厂沉淀的开发建议,还是很有借鉴意义的!
综上分析,决定使用 日志门面:SLF4J 日志实现:log4j2
3. 配置log4j2
3.1 引入Maven依赖
<!--日志相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
此时要排除默认自带的log组件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--以下是排除的组件-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 配置文件
配置文件的路径:src/main/resources/log4j2.xml

配置文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<!--
6个优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。
如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出
设置为OFF 表示不记录log4j2本身的日志,
-->
<!-- status:用来指定log4j本身的打印日志级别,monitorInterval:指定log4j自动重新配置的监测间隔时间 -->
<Configuration status="fatal">
<Properties>
<Property name="baseDir" value="${sys:user.home}/logs/Gangbb-Springboot-Log"/>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT"
onMismatch="DENY"/>
<PatternLayout
pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
</Console>
<!--debug级别日志文件输出-->
<RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在debug及以上在info以下 -->
<ThresholdFilter level="debug"/>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<!-- 策略 -->
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- info级别日志文件输出 -->
<RollingFile name="info_appender" fileName="${baseDir}/info.log"
filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在info及以上在error以下 -->
<ThresholdFilter level="info"/>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<!-- 策略 -->
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- error级别日志文件输出 -->
<RollingFile name="error_appender" fileName="${baseDir}/error.log"
filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在error及以上 -->
<ThresholdFilter level="error"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</Appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<Loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="debug_appender"/>
<AppenderRef ref="info_appender"/>
<AppenderRef ref="error_appender"/>
</Root>
</Loggers>
</Configuration>
配置好后重启下项目看看文件:
成功生成!
4. AOP统一打印Web请求和返回信息
4.1 引入AOP切面依赖
<!--AOP统一打印和返回信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 解析 UserAgent信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
4.2 定义打印请求和返回信息过滤器
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import eu.bitwalker.useragentutils.Version;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @author : Gangbb
* @ClassName : WebLogAspect
* @Description : 网络请求和响应过滤器
* @Date : 2021/1/11 22:11
*/
@Aspect
@Component
public class WebLogAspect {
/**
* 定义一个log用于打印
*/
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 切入点
*/
@Pointcut("execution(public * com.gangbb.gangbbspringbootlog.controller.*.*(..)))")
public void weblog(){
}
@Before("weblog()")
public void doBefore(JoinPoint joinPoint){
//收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取当前请求
HttpServletRequest request = attributes.getRequest();
//获取浏览器信息
String header = request.getHeader("User-Agent");
//转成UserAgent对象
UserAgent browser = UserAgent.parseUserAgentString(header);
//获取浏览器版本号
Version version = browser.getBrowser().getVersion(request.getHeader("User-Agent"));
//获取系统信息
OperatingSystem os = browser.getOperatingSystem();
//记录真正的内容
log.info("URL : " + request.getRequestURL().toString());
log.info("HTTP_METHOD :" + request.getMethod());
log.info("IP : " + request.getRemoteAddr());
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
if(browser.getBrowser()!= null && version != null){
log.info("BROWSER/VERSION : " + browser.getBrowser().getName() + "/" + version.getVersion());
}
if(os != null){
log.info("OS_NAME : " + os.getName());
}
}
//返回时拦截
@AfterReturning(returning = "res", pointcut = "weblog()")
public void doAfterReturning(Object res) throws JsonProcessingException {
//处理完请求,返回内容
//new ObjectMapper()用于对象转json
log.info("RESPONSE : " + new ObjectMapper().writeValueAsString(res));
}
}
4.3 请求测试查看打印信息


5. 参考文章
https://blog.youkuaiyun.com/weixin_34162228/article/details/88809217
https://blog.youkuaiyun.com/chengqiuming/article/details/105692373