为什么不直接使用日志框架,而是搞出一个日志门面?
日志门面(SLF4J)主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架来实现,例如log4j和logback等。 对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、log4j2、logback等),中间使用桥接器完成桥接。
前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性要求。有了SLF4J这个门面之后,程序员永远都是面向SLF4J编程,可以实现简单快速地替换底层的日志框架而不会导致业务代码需要做相应的修改
在使用 SLF4J 进行日志记录时,通常都需要在每个需要记录日志的类中定义 Logger 变量,如下所示:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
public class LogTestController {
private static final Logger logger = LoggerFactory.getLogger(LogTestController.class);
@GetMapping("/test")
public void test(){
logger.trace("Trace 日志...");
logger.debug("Debug 日志...");
logger.info("Info 日志...");
logger.warn("Warn 日志...");
logger.error("Error 日志...");
}
}
这显然属于重复性劳动,降低了开发效率,如果你在项目中引入了 Lombok,那么可以使用它提供的 @Slf4j 注解来自动生成上面那个变量,默认的变量名是 log,如果我们想采用惯用的 LOGGER 变量名,那么可以在工程的 main/java 目录中增加 lombok.config
文件,并在文件中增加 lombok.log.fieldName=LOGGER
的配置项即可
Spring Boot 默认的日志记录框架使用的是 Logback
SLF4J + Log4j2 是我们推荐的日志记录选型。
性能测试结果
细说各日志框架整合配置前,我们先来大致了解下,最常见的日志的几个级别:ERROR, WARN, INFO, DEBUG和TRACE。像其他的,比如ALL、OFF和FATAL之类的开发过程中应该基本上是不会涉及的。所以以下从低到高一次介绍下常见的日志级别。
日志级别从小到大为trace<debug<info<warn<error<fatal,由于通常日志框架默认日志级别设置为INFO,因此上面样例trace和debug级别的日志都看不到。
2020-08-17 13:59:16.566 INFO c.z.b.l.controller.LogTestController : Info 日志...
2020-08-17 13:59:16.566 WARN c.z.b.l.controller.LogTestController : Warn 日志...
2020-08-17 13:59:16.566 ERROR c.z.b.l.controller.LogTestController : Error 日志...
logback既可以通过application配置文件进行日志的配置,又可以通过logback-spring.xml进行日志的配置。通常情况下,使用全局配置文件application.yml或properties进行配置就足够了,如果您的日志输出需求特别复杂而且需求比较个性化,可以考虑使用logback-spring.xml的配置方式。
我们可以在applicaition.properties(yml) 文件中进行日志的配置
logging:
level:
root: info
com.dhy.boot.launch.controller: debug
file:
path: D:\logs
name: D:\logs\boot-launch.log
max-size: 10MB
max-history: 10
pattern:
console: '%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger : %msg%n'
logging.level.root=info
指定整个系统的默认日志级别是info,日志级别统一化logging.level.com.dhy.boot.launch.controller=debug
,指定某个特定的package的日志级别是debug,日志级别个性化。优先级角度,个性配置大于统一配置。logging.file.path
将日志输出到指定目录,如果不指定logging.file.name
,日志文件的默认名称是spring.log
。配置了logging.file.name
之后,logging.file.path
配置失效。# 日志文件大小
logging.file.max-size=10MB
分割的每个日志的文件最大容量,超过这个size之后日志继续分隔。logging.file.max-history=10
,以天为单位logging.pattern.file
输出到文件中的日志的格式logging.pattern.console
控制台输出日志的格式,为了在控制台调试时候显示效果更清晰,为日志增加了颜色。red、green等等这里日志文件会自动创建,无需手动指定
配合这张图,看一下占位符和logging.pattern.console
格式配置之间的关系
一般情况下,使用全局配置文件application.yml或properties进行配置就足够了,如果您的日志输出需求特别复杂,可以考虑使用logback-spring.xml的配置方式。
spring boot 用自带的logback打印日志,多环境打印:
打印Mybatis SQL,只需要把使用到Mybatis的package的日志级别调整为DEBUG,就可以将SQL打印出来。
前提:项目已经支持application.yml的profile多环境配置
因为logback是spring boot的默认日志框架,所以不需要引入maven依赖,直接上logback-spring.xml放在resources下面
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--引入默认的一些设置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--web信息-->
<logger name="org.springframework.web" level="info"/>
<!--写入日志到控制台的appender,用默认的,但是要去掉charset,否则windows下tomcat下乱码-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_PATH" value="D:/logs/boot-launch"/>
<!--写入日志到文件的appender-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名,每天一个文件-->
<FileNamePattern>${LOG_PATH}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--异步写日志到文件-->
<appender name="asyncFileAppender" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>500</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!--生产环境:打印控制台和输出到文件-->
<springProfile name="prod">
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="asyncFileAppender"/>
</root>
</springProfile>
<!--开发环境:打印控制台-->
<springProfile name="dev">
<!-- 打印sql -->
<logger name="com.dhy.boot.launch" level="DEBUG"/>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!--测试环境:打印控制台-->
<springProfile name="test">
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
异步日志配置:
上面配置完成之后,可以使用如下代码测试一下,是否满足需求
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
public class LogTestController {
private static final Logger logger = LoggerFactory.getLogger(LogTestController.class);
@GetMapping("/testlog")
public void test(){
logger.trace("Trace 日志...");
logger.debug("Debug 日志...");
logger.info("Info 日志...");
logger.warn("Warn 日志...");
logger.error("Error 日志...");
}
}
Spring Boot默认使用LogBack,但是我们没有看到显示依赖的jar包,其实是因为所在的jar包spring-boot-starter-logging都是作为spring-boot-starter-web或者spring-boot-starter依赖的一部分。
如果这里要使用Log4j2,需要从spring-boot-starter-web中去掉spring-boot-starter-logging依赖,同时显示声明使用Log4j2的依赖jar包,具体如下:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
另外log4j是之前使用比较广泛的软件,容易与log4j2发生冲突,如果冲突将log4j从相应的软件里面排除掉,比如:dozer
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.4.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
在resources目录下新建一个log4j2-spring.xml文件,放在src/main/resources目录下即可被Spring Boot应用识别。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<properties>
<!--日志输出位置-->
<property name="LOG_HOME">D:/logs</property>
</properties>
<Appenders>
<!-- 将日志输出到控制台-->
<Console name="CONSOLE" target="SYSTEM_OUT">
<!--设置日志格式及颜色-->
<PatternLayout pattern="[%style{%d}{bright,green}][%highlight{%p}][%style{%t}{bright,blue}][%style{%C}{bright,yellow}]: %msg%n%style{%throwable}{red}" disableAnsi="false" noConsoleNoAnsi="false"/>
</Console>
<!-- 将日志输出到文件-->
<RollingFile name="FILE-APPENDER" fileName="${LOG_HOME}/log4j2-demo.log" filePattern="${LOG_HOME}/log4j2-demo-%d{yyyy-MM-dd}-%i.log">
<!--设置日志格式-->
<PatternLayout>
<pattern>[%d][%p][%t][%C] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置最大存档数-->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 根日志设置 -->
<Root level="debug">
<AppenderRef ref="CONSOLE" level="debug"/>
<AppenderRef ref="FILE-APPENDER" level="info"/>
</Root>
<!--spring日志-->
<Logger name="org.springframework" level="info"/>
<!-- mybatis日志 -->
<Logger name="com.mybatis" level="warn"/>
</Loggers>
</configuration>
上文两个Appender,一个叫做CONSOLE用于输出日志到控制台,一个叫做FILE-APPENDER输出日志到文件
PatternLayout用于指定输出日志的格式,[%d][%p][%t][%C] %m%n
这些占位符将结合下文测试结果为大家介绍
Policies用于指定文件切分参数
< DefaultRolloverStrategy max="20" />
表示文件最大的存档数量,多余的将被删除
上文中的日志格式占位符号,在文末测试的时候为大家介绍。
但是我们通常会有这样一个需求,就是不同的环境使用不同的配置,比如:我们需要三个log4j2 xml文件:
但是Spring Boot
并不知道log4j2-< profile >.xml
这些配置文件是干什么的,所以需要通过在application.yml
文件中显示声明才行。
举例:在application-dev.yml里面使用log4j2-dev.xml配置文件
logging:
config: classpath:log4j2-dev.xml
以此类推,在application-prod.yml里面使用log4j2-prod.xml配置文件,在application-test.yml里面使用log4j2-test.xml配置文件。
结合下面代码说一下占位符
<PatternLayout pattern="[%style{%d}{bright,green}][%highlight{%p}][%style{%t}{bright,blue}][%style{%C}{bright,yellow}]: %msg%n%style{%throwable}{red}"
disableAnsi="false" noConsoleNoAnsi="false"/>
Log4j2基于LMAX公司开发Disruptor(一个开源的无锁并发框架),改善了Log4j和Logback在架构设计方面的缺陷,具有超高的吞吐量和低延迟。
<!-- Needed for Async Logging with Log4j 2 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
所以我们想获取log4j2异步日志带来的性能提升,要先引入disruptor
全局异步模式对日志的输出性能有非常大的提升,是官方推荐的异步日志输出方式。可以有两种方式为Spring Boot应用配置异步日志,第一种就是在应用启动类里面使用System.setProperty,代码如下:
@SpringBootApplication
public class BootLaunchApplication {
public static void main(String[] args) {
//下面语句使得Log4j2日志输出使用异步处理,减小输出日志对性能的影响
System.setProperty("Log4jContextSelector",
"org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
SpringApplication.run(BootLaunchApplication.class, args);
}
}
第二种是通过启动参数来设置全局异步日志
java -jar
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
boot-launch-1.0.jar
在以上配置完成之后,我们可以在log日志打印的位置下断点。当我们看到下图中红色边框部分的Log4j2-TF-1-AsyncLogger线程的时候,说明我们的全局异步日志配置成功了。
除了全局异步模式,slf4j还支持异步/同步混合模式。全局异步模式虽然是性能最好的日志输出方式,但是也是耗费主机资源最多的方式,如果你的应用服务器性能一般,你又想获得较好的日志输出性能,可以采用如下的方法。
采用异步/同步混合模式就不需要配置上面的Log4jContextSelector
在log4j2 xml
里面对Loggers
配置进行改造,加入AsyncLogger
也就是异步日志,只针对com.dhy.boot.launch
包(假如已知这个包对处理性能要求比较高)下的代码产生的日志采用异步模式,其他的日志仍然使用同步模式。
<Loggers>
<!-- 针对com.dhy.boot.launch包下面的日志采用异步日志 -->
<AsyncLogger name="com.dhy.boot.launch" level="debug" additivity="false">
<AppenderRef ref="CONSOLE" level="debug"/>
<AppenderRef ref="FILE-APPENDER" level="info"/>
</AsyncLogger>
<!-- 系统默认日志设置 -->
<Root level="debug">
<AppenderRef ref="CONSOLE" level="debug"/>
<AppenderRef ref="FILE-APPENDER" level="info"/>
</Root>
</Loggers>
@Data
public class AccessLog {
//访问者用户名
private String username;
//请求路径
private String url;
//请求消耗时长
private Integer duration;
//http 方法:GET、POST等
private String httpMethod;
//http 请求响应状态码
private Integer httpStatus;
//访问者ip
private String ip;
//此条记录的创建时间
private Date createTime;
}
通过自定义拦截器的方式,记录审计日志。
public class AccessLogInterceptor implements HandlerInterceptor {
//请求开始时间标识
private static final String LOGGER_SEND_TIME = "SEND_TIME";
//请求日志实体标识
private static final String LOGGER_ACCESSLOG = "ACCESSLOG_ENTITY";
private static final Logger logger = LoggerFactory.getLogger("ACCESS-LOG");
/** * 进入SpringMVC的Controller之前开始记录日志实体 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
//创建日志实体
AccessLog accessLog = new AccessLog();
// 设置IP地址
accessLog.setIp(AdrressIpUtils.getIpAdrress(request));
//设置请求方法,GET,POST...
accessLog.setHttpMethod(request.getMethod());
//设置请求路径
accessLog.setUrl(request.getRequestURI());
//设置请求开始时间
request.setAttribute(LOGGER_SEND_TIME,System.currentTimeMillis());
//设置请求实体到request内,方便afterCompletion方法调用
request.setAttribute(LOGGER_ACCESSLOG,accessLog);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
//获取本次请求日志实体
AccessLog accessLog = (AccessLog) request.getAttribute(LOGGER_ACCESSLOG);
//获取请求错误码,根据需求存入数据库,这里不保存
int status = response.getStatus();
accessLog.setHttpStatus(status);
//设置访问者(这里暂时写死)
// 因为不同的应用可能将访问者信息放在session里面,有的通过request传递,
// 总之可以获取到,但获取的方法不同
accessLog.setUsername("admin");
//当前时间
long currentTime = System.currentTimeMillis();
//请求开始时间
long snedTime = Long.valueOf(request.getAttribute(LOGGER_SEND_TIME).toString());
//设置请求时间差
accessLog.setDuration(Integer.valueOf((currentTime - snedTime)+""));
accessLog.setCreateTime(new Date());
//将sysLog对象持久化保存
logger.info(accessLog.toString());
}
}
上文中LoggerFactory.getLogger("ACCESS-LOG")
获取一个日志配置中的Logger
的名字,用于打印日志输出,持久化到日志文件里面。"ACCESS-LOG"
的日志Logger
定义我们下文中介绍。
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
//设置排除路径,spring boot 2.*,注意排除掉静态资源的路径,不然静态资源无法访问
private final String[] excludePath = {"/static"};
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AccessLogInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePath);
}
}
上面的代码中涉及到一个工具类,用于获取请求客户端的ip。代码如下:
public class AdrressIpUtils {
/** * 获取Ip地址 * @param request HttpServletRequest * @return ip */
public static String getIpAdrress(HttpServletRequest request) {
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = XFor.indexOf(",");
if(index != -1){
return XFor.substring(0,index);
}else{
return XFor;
}
}
XFor = Xip;
if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
return XFor;
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getRemoteAddr();
}
return XFor;
}
}
下文中的配置参考以Log4J2配置为例
LoggerFactory.getLogger("ACCESS-LOG")
name
为ACCESS-LOG
的Logger
配置。Logger
是一个AsyncLogger
,指向的输出目标是ACCESS-APPENDER
ACCESS-APPENDER
是一个日志文件输出配置,日志文件是access-log.log
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<properties>
<!--日志输出位置-->
<property name="LOG_HOME">D:/logs</property>
</properties>
<Appenders>
<!-- 将日志输出到文件-->
<RollingFile name="ACCESS-APPENDER" fileName="${LOG_HOME}/access.log" filePattern="${LOG_HOME}/access-%d{yyyy-MM-dd}-%i.log">
<!--设置日志格式-->
<PatternLayout>
<pattern>[%d][%p][%t][%C] %m%n</pattern>
</PatternLayout>
<Policies>
<!-- 设置日志文件切分参数 -->
<SizeBasedTriggeringPolicy size="100MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
<!--设置最大存档数-->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<AsyncLogger name="ACCESS-LOG" level="debug" additivity="false">
<AppenderRef ref="ACCESS-APPENDER" level="info"/>
</AsyncLogger>
</Loggers>
</configuration>
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_53157173/article/details/121595927
内容来源于网络,如有侵权,请联系作者删除!