Spring Boot 日志整合
1. 日志框架
java的日志框架很多,而且不同的开源项目可能使用了不同的日志框架,如Spring 使用Apache commons-logging,Hibernate使用jboss-logging。Spring Boot最为多项目的整合是如何整合这些框架的日志的来实现大一统的呢?
市面上的日志框架;
JUL(java.util.logging)、JCL(Jakarta Commons Logging)、Jboss-logging、logback、log4j、log4j2、slf4j、 commons-logging…
为了统一各个日志框架,就要抽象一个一致的、相同的、公共的接口,就行数据库连接一样,接口是一样的,不管你使用什么语言实现链接,如Java的JDBC。
日志门面 (日志的抽象层) | 日志实现 |
---|---|
JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-logging | Log4j JUL(java.util.logging) Log4j2 Logback commons-logging |
左边选一个门面(抽象层)、右边来选一个实现;
最常用的选择例如:
日志门面: SLF4J;
日志实现:Logback、Log4j2;
SpringBoot选用SLF4j和Logback;
Log4j的作者嫌Log4j写的不好,于是又写了一个Logback,同时又写了一个SLF4j,所以Logback是SLF4j天然实现无需适配层(其他的日志框架要引入适配层)。Log4j2是apache的一个开源日志框架,名字看起来是Log4j的后继版本,其实没啥关系。Log4j据报道有很多问题,现在几乎没人用了,典型的问题如线程死锁等。还有人测试Logback和Log4j2,结果在Log4j2在异步模式下性能遥遥领先,尤其是多核CPU的服务器,随着核数的增加,性能大幅提升。所以项目推荐使用Log4j2。 官方性能测试
2. SLF4j使用
2.1 如何在系统中使用SLF4j
开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法,给系统里面导入slf4j的jar和logback的实现jar
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@RestController
@RequestMapping(value = "/myDemo")
public class MyDemoController {
private static Logger logger = LogManager.getLogger(MyDemoController.class.getName());
@RequestMapping(value = "/defaultParams", method = RequestMethod.GET)
public User userList(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
logger.info(request.getRequestURL());
User user = new User();
user.setNickname("wahaha");
user.setAge(23);
user.setGender("female");
model.addAttribute(user);
return user;
}
}
从图中可以看出Logback本身就是SLF4J的实现,但如果SLF4J要整合Log4j则需要引入Adaptation layer,即slf4j-log412.jar。SLF4J要整合Log4j2或其他的日志实现思想是一样的,要引入Adaptation layer。
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件; 如log4j的log4j.properties,log4j2的log4j2.xml。
2.2 如何实现日志统一
例如我们的项目是使用Spring Boot时整合JPA(Hibernate)的项目,那么就是有Spring Boot的Logback、Spring的commons-logging、Hibernate的jboss-logging,那么我们如何使用同一(桥接遗留)的日志抽象层
参考文档:Bridging legacy APIs
思想就是偷梁换柱,举例:Spring使用commons-logging,要想让Spring使用Logback(Springboot的)
需要把commons-logging替换掉,替换为如图的说明 “jcl-over-slf4j.jar replaces commons-logging.jar” ,仔细想想应该是同包同类同方法不同实现(没有具体研究过),这样最终都调用到了Logback。
2.3 Spring Boot 日志整合
这工程的父pom.xml中:
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
IDEA查看一下依赖关系:
jcl-over-slf4j、log4j-over-slf4j、jul-to-slf4j替换了commons-logging、log4j、java.util.logging
其实如果你分析pom你会发现再导入Spring时已经剔除了commons-logging
可以得出日志整合三部曲:
-
将系统中其他日志框架先排除出去
-
用中间包来替换原有的日志框架
-
我们导入slf4j其他的实现
3. Spring Boot 日志使用
3.1 SpringBoot修改日志的默认配置
application.properties:
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.path=/spring/log
# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} --- [%thread] --- %-5level --- %logger{50} --- %msg%n
logging.file | logging.path | Example | Description |
---|---|---|---|
(none) | (none) | 只在控制台输出 | |
指定文件名 | (none) | my.log | 输出日志到当前项目下my.log文件 |
(none) | 指定目录 | /var/log | 输出到指定目录/var/log的 spring.log 文件中 |
3.2 指定配置
给 类路径 下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
默认的在:
自定义的日志文件名:
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
<!--可以指定某段配置只在某个环境下生效-->
</springProfile>
示例:
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ---> [%thread] --> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ===> [%thread] ===> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
</layout>
</appender>
3.3 日志框架切换
切换为log4j2 pom修改如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
在类路径下放log4j2.xml
可以参考jar包的org/springframework/boot/logging/log4j2/log4j2-file.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<!--
%d: 日期
%t: 线程名
%15.15t: 线程名不超过15个字符左边补空格,超过从开通截取15个字符
%c: 一般指类名
%-4.4L: 日志记录行号(慎用,消耗性能)
%-30.30M: 日志记录的方法(慎用,消耗性能)
%-40.40c{1.}: 包名只写首字母,类路径不够40字符使用空格右边补全,超过40字符从开头截取40个
%m: 日志信息
%n: 系统对应换号
%xwEx: Spring Boot 自定义的异常转换
%5p: 日志级别占5个字符
-->
<Property name="PID">????</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="CONSOLE_LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} #%-4.4L--> %-30.30M %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
<Property name="FILE_LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" />
</Console>
<!--$${date:yyyy-MM} see http://logging.apache.org/log4j/2.x/manual/lookups.html#DateLookup:-->
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>${sys:FILE_LOG_PATTERN}</Pattern>
</PatternLayout>
<Policies>
<!--超过10M会压缩一个.log.gz文件-->
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
<Root level="info">
<!--http://logging.apache.org/log4j/2.x/faq.html#config_sep_appender_level-->
<AppenderRef ref="Console" level="INFO"/>
<AppenderRef ref="File" />
</Root>
</Loggers>
</Configuration>
3.4 log4j2
参考官方文档 Welcome to Log4j 2!
PatternLayout–>Patterns–>Conversion Pattern:
Conversion Pattern | Logger Name | Result |
---|---|---|
%c{1} | org.apache.commons.Foo | Foo |
%c{2} | org.apache.commons.Foo | commons.Foo |
%c{10} | org.apache.commons.Foo | org.apache.commons.Foo |
%c{-1} | org.apache.commons.Foo | apache.commons.Foo |
%c{-2} | org.apache.commons.Foo | commons.Foo |
%c{-10} | org.apache.commons.Foo | org.apache.commons.Foo |
%c{1.} | org.apache.commons.Foo | o.a.c.Foo |
%c{1.1..} | org.apache.commons.test.Foo | o.a...Foo |
%c{.} | org.apache.commons.test.Foo | …Foo |
highlight --> color:
日志的行号和方法名很消耗性能
l/location
Outputs location information of the caller which generated the logging event.
The location information depends on the JVM implementation but usually consists of the fully qualified name of the calling method followed by the callers source the file name and line number between parentheses.
Generating location information is an expensive operation and may impact performance. Use with caution.
L/line
Outputs the line number from where the logging request was issued.
Generating line number information (location information) is an expensive operation and may impact performance. Use with caution.
M/method
Outputs the method name where the logging request was issued.
Generating the method name of the caller (location information) is an expensive operation and may impact performance. Use with caution.
**异常处理 xEx|xException|xThrowable **
{
[“none” | “short” | “full” | depth]
[,filters(package,package,…)]
[,separator(separator)]
}
{ansi(
Key=Value,Value,…
Key=Value,Value,…
…)
}
{suffix(pattern)}
The same as the %throwable conversion word but also includes class packaging information.
Pattern Converters
Format modifier | left justify | minimum width | maximum width | comment |
---|---|---|---|---|
%20c | false | 20 | none | Left pad with spaces if the category name is less than 20 characters long. |
%-20c | true | 20 | none | Right pad with spaces if the category name is less than 20 characters long. |
%.30c | NA none | 30 | Truncate | from the beginning if the category name is longer than 30 characters. |
%20.30c | false | 20 | 30 | Left pad with spaces if the category name is shorter than 20 characters. However, if category name is longer than 30 characters, then truncate from the beginning. |
%-20.30c | true | 20 | 30 | Right pad with spaces if the category name is shorter than 20 characters. However, if category name is longer than 30 characters, then truncate from the beginning. |
%-20.-30c | true | 20 | 30 | Right pad with spaces if the category name is shorter than 20 characters. However, if category name is longer than 30 characters, then truncate from the end. |