参考官方日志文档链接1
参考官方日志文档链接2
http://www.51gjie.com/javaweb/1096.html
1.日志框架基本介绍
常见的日志框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j...
- JCL(Jakarta Commons Logging) :spring 底层所使用的日志框架
- JUL(java.util.logging) :java提供的日志框架,是JDK在1.4版本中引入的Java原生日志框架
- Jboss-logging :就是jboss提供的日志框架(hibernate)
- log4j :Apache的一个开源项目,可以控制日志信息输送的目的地是控制台、文件、GUI组件等,可以控制每一条日志的输出格式,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。虽然已经停止维护了,但目前绝大部分企业都是用的log4j。
- logback :是由 Log4j 的作者设计完成的,拥有更好的特性,用来取代 Log4j 的一个日志框架,是 SLF4j 的原生实现
- log4j2:已经不仅仅是 Log4j 的一个升级版本了,它从头到尾都被重写了。由于使用了 LMAX Disruptor 技术(无锁异步),使得日志的吞吐量、性能比 log4j 1.x 提高 10 倍,并解决了一些死锁的 bug,而且配置更加简单灵活。
- slf4j(Simple Logging Facade for Java ):简单的日志框架,也是logback ,log4j 的作者写的,主要是目前日志的抽象层
日志门面 (日志的抽象层) | 日志实现 |
---|---|
JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-logging | Log4j 、JUL(java.util.logging)、Log4j2 、Logback |
2.springboot日志框架选择
SpringBoot 默认选择的是 SLF4J + Logback 的组合,本篇文章讲解的是默认的日志
在实际项目中我们可以整合 Log4j2 来代替原来的 Logback(形成 SLF4J + Log4j2 组合)。采用 Log4j2 有如下几个优点:
- 相比与其他的日志系统,log4j2 丢数据的情况少;
- 采用 disruptor 技术,在多线程环境下,性能高于 logback 等 10 倍以上(无论在同步日志模式还是异步日志模式下,在所有日志框架中 log4j2 性能都是最佳的);
- 利用 jdk1.5 并发的特性,减少了死锁的发生;
3.日志级别
trace<debug<info<warn<error<fatal
⚠️:Logback does not have a FATAL level. It is mapped to ERROR.(Logback没有FATAL级别,映射到ERROR)
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//日志的级别;
//由低到高 trace<debug<info<warn<error
//可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别(默认只有下面输出信息)
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}
4.maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
5.控制台输出基本介绍
2019-03-05 10:57:51.112 INFO 45469 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms
2019-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2019-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
输出内容:
• 时间日期:精确到毫秒,易于排序
• 日志级别:ERROR, WARN, INFO, DEBUG ,TRACE
• 进程ID:图中45469
• 分隔符:--- 分隔符用于区分实际日志消息的开始
• 线程名:方括号括起来(在控制台输出时可能被截断,也就是展示不全)
• Logger名:通常使用源代码的类名(通常缩写)
• •日志消息
6.颜色配置
如果你的终端支持ANSI,彩色输出是用来帮助可读性。你可以设置spring.output.ansi.enabled来改变默认的自动检测(detect):设置值
- ALWAYS
开启彩色输出 - DETECT
尝试检测ANSI 颜色是否支持 - NEVER
关闭彩色输出
彩色编码使用%clr转换字配置,最简单的情势是根据日志级别进行色彩输出,例如:
%clr(%5p)
日志级别到颜色的映射:
Level | Color |
---|---|
FATAL | Red |
ERROR | Red |
WARN | Yellow |
INFO | Green |
DEBUG | Green |
TRACE | Green |
你可以通过在转换器中提供选项来指定应当试用的色彩或风格。例如,为了使文本显示黄色:
%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}
支持的颜色:
• blue
• cyan
• faint
• green
• magenta
• red
• yellow
7.SpringBoot修改日志的默认配置
① 设置日志级别
logging.level设置日志级别,后面跟生效的区域,比如root表示整个项目,也可以设置为某个包下,也可以具体到某个类名(日志级别的值不区分大小写)
#日志的最低输出级别调整,设置在com.xx包下
logging.level.com.xx=trace
#root日志以WARN级别输出(让日志只输出warn及以上级别的信息)
logging.level.root=warn
#springframework.web日志以DEBUG级别输出
logging.level.org.springframework.web=DEBUG
#hibernate日志以ERROR级别输出
logging.level.org.hibernate=ERROR
② 日志路径
# logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
# logging.file=/usr/xx/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 | 输出到指定目录的 spring.log 文件中 |
指定文件名 | 指定目录 | 输出到文件名,前一个配置生效 |
当日志文件到达10Mb时会循环保存。文件的大小限制可通过logging.file.max-size属性设置。之前保存的日志文件会无限期存档,除非配置logging.file.max-history属性
③ 日志输出格式
%d:表示日期时间,
%thread:表示线程名,
%-5level:级别从左显示5个字符宽度,
%logger{50}:表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n:换行符
实例:%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
8.自定义日志配置
① 配置文件名称
在xml文件中,指定日志文件
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 |
建议将-spring变体用于日志记录配置(例如,logback-spring.xml而不是logback.xml)。如果使用标准配置位置,Spring将无法完全控制日志初始化
如果你既想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:
logging.config=classpath:logback-config.xml
- Spring Boot包含了许多可以帮助进行高级配置的Logback扩展,您可以在logback-spring.xml配置文件中使用这些扩展名
- 由于标准的logback.xml配置文件加载的太早,所以你不能在里面使用扩展部分,您需要使用logback-spring.xml或定义一个logging.config属性
② springProfile标签针对不同开发环境
使用<springProfile>标签,您可以根据活动的Spring配置文件选择包括或排除配置部分
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
<springProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
③ Environment Properties
该<springProperty>标签允许您公开来自Spring环境的属性,以便在Logback中使用。如果要从application.properties中访问文件中的值,这样做很有用。该标签的工作方式与Logback的<property>标签类似。但是,不是指定直接的值value,而是指定属性的源source(来自Environment)。如果需要将该属性存储在局部作用域之外的其他地方,则可以使用scope属性。如果需要回退值(在Environment中没有设置该属性的情况下),可以使用defaultValue属性。以下示例显示如何公开用于Logback的属性
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
<remoteHost>${fluentHost}</remoteHost>
...
</appender>
④ 配置详解
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称 -->
<contextName>logback</contextName>
<!--property:用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
<property name="LOG_PATH" value="/usr/xx/log.log" />
<!--从配置文件中读取logging.path属性值-->
<springProperty name="LOG_HOME" source="logging.path"/>
<!--在pattern中直接用${pattern_format}调用公共部分-->
<property name="pattern_format" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n" />
<!-- <appender>:用来格式化日志输出节点,有两个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。-->
<!--输出到控制台 ConsoleAppender -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--ThresholdFilter为系统定义的拦截器,此日志appender是为开发使用,只配置最低级别,此配置表示控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<!--设置输出格式 class可省略-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n</pattern>
<!--配置颜色,可使用下面-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %blue(%logger{50}) - %highlight(%msg) %n</pattern>
<!--设置编码,可省略-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件,时间窗口滚动-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名,指定最新的文件名,其他文件名使用FileNamePattern -->
<File>${LOG_PATH}/info.log</File>
<!--文件滚动模式-->
<!--第一种-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名,可设置文件类型为gz,开启文件压缩,初始i为0-->
<FileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数,也就是过期时间-->
<MaxHistory>15</MaxHistory>
<!--单个日志文件最大值,达到之后就进行切割-->
<maxFileSize>10MB</maxFileSize>
<!--日志文件总体的最大值,达到之后,就会删除旧的日志-->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<!--关系:SizeAndTimeBasedRollingPolicy extends TimeBasedRollingPolicy-->
<!--第二种,因TimeBasedRollingPolicy下没有maxFileSize,像上面那样配置后会报错-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>15</MaxHistory>
<totalSizeCap>1GB</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!--
上面配置意思:每天产生一个日志文件,如果超出了10M,日志就进行切割,并且在日志文件名称上加一,新的日志存入新的i++文件中;
日志文件最多保持15天,日志文件总共最大为1G;
当超过1G时候,删除旧的日志文件,在我测试时候发现,如果某天的日志文件分割了,会先删除i=1的,再删除i=0,接着i=2,3....
-->
<!--输出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!--设置编码,可省略-->
<charset>UTF-8</charset>
</encoder>
<!-- 过滤器,过滤掉不是指定日志水平的日志,精确定位 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志级别 -->
<level>INFO</level>
<!-- 如果跟该日志水平相匹配,则接受 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果跟该日志水平不匹配,则过滤掉 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 指定主日志输出级别,只有一个level属性-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
<!--<loger>指定自定义日志的级别,有name,level,addtivity属性-->
<!-- logback为java中的包 -->
<logger name="com.xx.controller"/>
<!--
name:用来指定受此loger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还 有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
addtivity:是否向上级loger传递打印信息。默认是true。
-->
<logger name="com.xx.controller.SpringController" level="WARN" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
root 指定的日志级别就是用类进行输出的日志,例如:
private static Logger log = LoggerFactory.getLogger(logbackTest.class);
⑤ loger的使用:
public class TestController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/show")
public void show() {
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}
}
<root level="info">
<appender-ref ref="console" />
</root>
- 第一种:带有loger的配置,不指定级别,不指定appender
<logger name="com.mine.demo"/>
将控制demo包下的所有类的日志的打印,但是并没用设置打印级别,所以继承他的上级的日志级别,即root的级别“info”;
没有设置addtivity,默认为true,将此loger的打印信息向上级传递,设置为false时候,执行/show,show()方法执行,但是不打印日志信息;
没有设置appender,此loger本身不打印任何信息。
当执行该包下的某个类中的功能时,将方法内所有日志信息传递给root,本身并不打印;
root接到下级传递的信息,交给已经配置好的名为“console”的appender处理,“console”appender将信息打印到控制台。
此配置类似:<logger name="com.mine.demo.TestDemoController" level="info" additivity="true">
结果:
2020-10-09 15:03:57.680 logback [http-nio-8080-exec-1] INFO com.mine.demo.TestDemoController - 这是info日志...
2020-10-09 15:03:57.680 logback [http-nio-8080-exec-1] WARN com.mine.demo.TestDemoController - 这是warn日志...
2020-10-09 15:03:57.681 logback [http-nio-8080-exec-1] ERROR com.mine.demo.TestDemoController - 这是error日志...
当additivity=false时候,本身打印,但是没有设置appender,此loger本身不打印任何信息。
- 第二种:带有多个loger的配置,指定级别,指定appender
<logger name="com.mine.demo.TestDemoController" level="WARN" additivity="false">
<appender-ref ref="console"/>
</logger>
控制com.mine.demo.TestDemoController类的日志打印,打印级别为“WARN”;
additivity属性为false,表示此loger的打印信息不再向上级传递;
指定了名字为“console”的appender;
当执行TestDemoController中的方法时将级别为“WARN”及大于“WARN”的日志信息交给此loger指定的名为“console”的appender处理,在控制台中打出日志,不再向上级root传递打印信息。
结果:
2020-10-09 14:42:19.792 logback [http-nio-8080-exec-1] WARN com.mine.demo.TestDemoController - 这是warn日志...
2020-10-09 14:42:19.792 logback [http-nio-8080-exec-1] ERROR com.mine.demo.TestDemoController - 这是error日志...
<logger name="com.mine.demo.TestDemoController" level="warn" additivity="true">
<appender-ref ref="console"/>
</logger>
<logger name="com.mine.demo.TestDemoController" level="info" additivity="false"/>
多个个同时存在时候,后一个覆盖前一个,level,additivity,appender后一个有则覆盖,没有则使用前一个配置,所以上面的level="debug", additivity="false", <appender-ref ref="console"/>
结果:
2020-10-09 15:03:57.680 logback [http-nio-8080-exec-1] INFO com.mine.demo.TestDemoController - 这是info日志...
2020-10-09 15:03:57.680 logback [http-nio-8080-exec-1] WARN com.mine.demo.TestDemoController - 这是warn日志...
2020-10-09 15:03:57.681 logback [http-nio-8080-exec-1] ERROR com.mine.demo.TestDemoController - 这是error日志...
<root level="warn">
<appender-ref ref="console" />
</root>
<logger name="com.mine.demo.TestDemoController" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
结果:
2020-10-09 15:30:12.696 logback [http-nio-8080-exec-1] INFO com.mine.demo.TestDemoController - 这是info日志...
2020-10-09 15:30:12.696 logback [http-nio-8080-exec-1] INFO com.mine.demo.TestDemoController - 这是info日志...
2020-10-09 15:30:12.698 logback [http-nio-8080-exec-1] WARN com.mine.demo.TestDemoController - 这是warn日志...
2020-10-09 15:30:12.698 logback [http-nio-8080-exec-1] WARN com.mine.demo.TestDemoController - 这是warn日志...
2020-10-09 15:30:12.698 logback [http-nio-8080-exec-1] ERROR com.mine.demo.TestDemoController - 这是error日志...
2020-10-09 15:30:12.698 logback [http-nio-8080-exec-1] ERROR com.mine.demo.TestDemoController - 这是error日志...
开始理解的时候是本身打印info级别,向上级root传递的时候,上级是warn级别的,所以打印是本身打印info级别以上的,上级只打印筛选的warn级别以上的。结果多次测试发现不是这样。
后来理解:当本身的打印日志信息传递给上级,上级只负责输出。所以当本身打印info日志,向上级root传递info信息,上级继续打印info日志;打印warn日志......依次类推
9.最终配置logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 存放日志文件的路径的根目录 -->
<springProperty name="log_path" source="custom.logging.path"/>
<!--输出到控制台 ConsoleAppender -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<!--设置输出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--文件输出,时间窗口滚动-->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${log_path}/info.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<MaxHistory>15</MaxHistory>
<maxFileSize>10MB</maxFileSize>
<totalSizeCap>2GB</totalSizeCap>
</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>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_path}/error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_path}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<!-- 此日志文档只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<springProfile name="dev">
<root level="info">
<appender-ref ref="console"/>
</root>
<logger name="com.xx.dao" level="debug" />
</springProfile>
<springProfile name="test,prod">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="info"/>
<appender-ref ref="error"/>
</root>
</springProfile>
</configuration>