日志

1.基础

1.1.介绍

目前Java应用常用的日志框架log4j, apache commons logging,log4j2, logBack,slf4j, JUL等。

分布式环境针对日志实时分析统计的日志,一般采用apache flume、facebook scribe等

1.日志框架:提供日志调用的接口,实际的日志输出托付给日志系统实现。

  • JCL(Jakarta Commons Logging):比較流行的日志框架,非常多框架都依赖JCL,比如Spring等。
  • SLF4j:作为日志门面,初衷是配合Logback使用,但同一时候兼容Log4j,比如实现类slf4j-log4jl2。当前作为统一的日志工具,底层可以随意切换logback和log4j。

2.日志系统:负责输出日志

  • Log4j:经典的一种日志解决方式。内部把日志系统抽象封装成Logger 、appender 、pattern 等实现。我们能够通过配置文件轻松的实现日志系统的管理和多样化配置。
  • Log4j2:Log4j的2.0版本号。对Log4j进行了优化。比方支持參数API、支持异步appender、插件式架构等
  • Logback:Log4j的替代产品。须要配合日志框架SLF4j使用
  • JUL(java.util.logging):JDK提供的日志系统。较混乱,不经常使用

3.Spring Boot项目的日志集成方案有以下几种:

  • Logback:Logback是Spring Boot默认的日志框架,它是Log4j框架的改进版,具有更好的性能和可靠性。Logback提供了丰富的配置选项,可以灵活地配置日志输出格式、级别、滚动策略等。
  • Log4j2:Log4j2是Log4j框架的升级版,它提供了更好的性能和可靠性,并支持异步日志记录。在Spring Boot中,可以使用Log4j2作为替代Logback的日志框架。
  • JDK Logging:JDK Logging是Java标准库中提供的日志框架,它提供了基本的日志记录功能,并与Java平台紧密集成。在Spring Boot中,可以使用JDK Logging作为替代Logback的日志框架。
  • SLF4J:SLF4J是一个日志门面(Logging Facade),它提供了统一的API,可以在运行时选择不同的日志实现。在Spring Boot中,可以使用SLF4J作为替代Logback的日志门面,并配合其他日志实现(如Log4j2等)使用。

需要注意的是,Spring Boot默认使用Logback作为日志框架,但您可以使用上述任何一种日志框架替代它。如果您需要更高级的日志记录功能,例如异步日志记录、分布式日志记录等,您可以使用其他日志框架或扩展来满足需求。

项目中可选的日志方案:其中SLF4J作为日志的门面统一管理api

  • SLF4J+ Logback (简单的日志需求)
  • SLF4J+ Log4j2 (高级的日志需求)

4.maven依赖 Logback 和 Log4j2不能同时引入,会造成类冲突。因为SLF4J自动选择日志框架进行加载。

<properties>
    <slf4j.version>1.7.25</slf4j.version>
    <logback.version>1.2.3</logback.version>
    <log4j.version>2.10.0</log4j.version>
</properties>

<dependencies>
    <!--推荐使用slf4j作为门面-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>

    <!-- logback -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>${logback.version}</version>
    </dependency>

    <!-- log4j2 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>${log4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-web</artifactId>
        <version>${log4j.version}</version>
    </dependency>

    <!--log4j2和slf4j的连接包 需要绑定到log4j2 core核心包上-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    
    <!--log4j2 异步appender须要-->
    <dependency>
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>3.2.0</version>
    </dependency>
</dependencies>

1.2.日志级别

  • TRACE:追踪。一般上对核心系统进行性能调试或者跟踪问题时有用,此级别很低,一般上是不开启的,开启后日志会很快就打满磁盘的。
  • DEBUG:调试。这个大家应该不陌生了。开发过程中主要是打印记录一些运行信息之类的。
  • INFO:信息。这个是最常见的了,大部分默认就是这个级别的日志。一般上记录了一些交互信息,一些请求参数等等。可方便定位问题,或者还原现场环境的时候使用。此日志相对来说是比较重要的。
  • WARN:警告。这个一般上是记录潜在的可能会引发错误的信息。比如启动时,某某配置文件不存在或者某个参数未设置之类的。
  • ERROR:错误。这个也是比较常见的,一般上是在捕获异常时输出,虽然发生了错误,但不影响系统的正常运行。但可能会导致系统出错或是宕机等。

2.log4j2

官网:https://logging.apache.org/log4j/2.x/

2.1.配置

log4j 2.x版本不再支持像1.x中的.properties后缀的文件配置方式,2.x版本配置文件后缀名只能为".xml",".json"或者".jsn".

系统选择配置文件的优先级(从先到后)如下:

  • classpath下的名为log4j2-test.json 或者log4j2-test.jsn的文件.
  • classpath下的名为log4j2-test.xml的文件.
  • classpath下名为log4j2.json 或者log4j2.jsn的文件.
  • classpath下名为log4j2.xml的文件.

2.2.xml配置说明

<?xml version="1.0" encoding="UTF-8"?>
 <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
 <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
 <configuration status="WARN" monitorInterval="30">
     <!--先定义所有的appender-->
     <appenders>
     <!--这个输出控制台的配置-->
         <console name="Console" target="SYSTEM_OUT">
         <!--输出日志的格式-->
             <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
         </console>
     <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
     <File name="log" fileName="log/test.log" append="false" >
        <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
     </File>
     <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
         <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
                      filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
             <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->        
             <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
             <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
             <Policies>
                 <TimeBasedTriggeringPolicy/>
                 <SizeBasedTriggeringPolicy size="100 MB"/>
             </Policies>
         </RollingFile>
         <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
                      filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
             <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
             <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
             <Policies>
                 <TimeBasedTriggeringPolicy/>
                 <SizeBasedTriggeringPolicy size="100 MB"/>
             </Policies>
         <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
             <DefaultRolloverStrategy max="20"/>
         </RollingFile>
         <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
                      filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
             <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
             <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
             <Policies>
                 <TimeBasedTriggeringPolicy/>
                 <SizeBasedTriggeringPolicy size="100 MB"/>
             </Policies>
         </RollingFile>
     </appenders>
     <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
     <loggers>
         <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
         <logger name="org.springframework" level="INFO"></logger>
         <logger name="org.mybatis" level="INFO"></logger>
         <!-- 自定义包下设置为INFO,则可以看见输出的日志不包含debug输出了 -->
         <logger name="cn.lqdev.learning" level="INFO"/>
         <root level="all">
             <appender-ref ref="Console"/>
             <appender-ref ref="RollingFileInfo"/>
             <appender-ref ref="RollingFileWarn"/>
             <appender-ref ref="RollingFileError"/>
         </root>
     </loggers>
 </configuration>

2.3.properties配置说明

# log4j2内部自身的日志级别
status = error
# 配置日志信息输出到哪里:err表示作为标准错误输出,还可以是一个文件路径或者一个URL
dest = err
# 配置名称
name = PropertiesConfig

# 自定义属性名称,以便在之后的配置中使用,如${filename} 
property.filename = target/rolling/rollingtest.log
 
# 级别过滤(过滤日志记录) 
filter.threshold.type = ThresholdFilter
# 只记录debug级别以上的日志,大小写都可以
filter.threshold.level = debug

# 控制台类型的日志输出源 
appender.console.type = Console
# 名称 :唯一
appender.console.name = STDOUT
# 布局类型
appender.console.layout.type = PatternLayout
# 输出模板格式 这是springboot
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -%5p ${PID:-} [%15.15t] %C{1.} [%t]: %m%n
# 过滤级别 ThresholdFilter:低于级别的不输出
appender.console.filter.threshold.type = ThresholdFilter
# 记录日志的级别
appender.console.filter.threshold.level = info
 
 # 滚动文件,会根据配置如文件大小或者时间自动生成一个新文件
appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
# 日志文件名称
appender.rolling.fileName = ${filename}
# 日志回滚的文件命名规则
appender.rolling.filePattern = target/rolling2/test1-%d{MM-dd-yyyy}-%i.log.gz
appender.rolling.layout.type = PatternLayout
# 输出格式
appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
# 滚动日志的策略,即设置何时新建日志文件输出日志
appender.rolling.policies.type = Policies
# 设置时间 
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
# 指定多久滚动一次 单位跟着filePattern配置精度最后一位 ,这里就是2秒了
appender.rolling.policies.time.interval = 2
appender.rolling.policies.time.modulate = true
# 日志文件的大小 
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
# 为了测试设置小一点
appender.rolling.policies.size.size=1kb

##  还有其他的触发条件如:CronTriggeringPolicy 按Cron表达式进行的 具体使用可自行搜索

# 指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的
appender.rolling.strategy.type = DefaultRolloverStrategy
# 最多5个文件,其实还是看filePattern的i的值,当大于5后,就会自动覆盖了
appender.rolling.strategy.max = 5
 
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

2.4.格式化符号说明

######### 常见参数 #########
//
%c{参数} 或 %logger{参数}  ##输出日志名称
%C{参数} 或 %class{参数    ##输出类型
%d{参数}{时区te{参数}{时区} ##输出时间,如:%d{yyyy-MM-dd HH:mm:ss, SSS}
%F|%file                  ##输出文件名
highlight{pattern}{style} ##高亮显示
%l  ##输出错误的完整位置
%L  ##输出错误行号
%m 或 %msg 或 %message ##输出错误信息
%M 或 %method ##输出方法名
%n            ##输出换行符
%level{参数1}{参数2}{参数3} ##输出日志的级别
%t 或 %thread              ##创建logging事件的线程名
*/

######### 特殊符号 ############
#有些特殊符号不能直接打印,需要使用实体名称或者编号
//
& —— &amp; 或者 &#38;
< —— &lt;  或者 &#60;
> —— &gt;  或者 &#62;
" —— &quot; 或者 &#34;
‘ —— &apos; 或者 &#39;
*/

######## pattern对齐修饰 ##########

// 对齐修饰,可以指定信息的输出格式,如是否左对齐,是否留空格等。
## 编写格式为在任何pattern和%之间加入一个小数,可以是正数,也可以是负数。
## 整数表示右对齐,负数表示左对齐;
## 整数位表示输出信息的最小n个字符,如果输出信息不够n个字符,将用空格补齐;
## 小数位表示输出信息的最大字符数,如果超过n个字符,则只保留最后n个字符的信息
## (注意:保留的是后20个字符,而不是前20个字符)
*/

#示例如下
//

%20 —— 右对齐,不足20个字符则在信息前面用空格补足,超过20个字符则保留原信息
%-20 —— 左对齐,不足20个字符则在信息后面用空格补足,超过20个字符则保留原信息
%.30 —— 如果信息超过30个字符,则只保留最后30个字符
%20.30 —— 右对齐,不足20个字符则在信息前面用空格补足,超过30个字符则只保留最后30个字符
%-20.30 —— 左对齐,不足20个字符则在信息后面用空格补足,超过30个字符则只保留最后30个字符

3.Logback

官网:https://logback.qos.ch/index.html

logback是由log4j创始人设计的又一个开源日志组件。同时也是SpringBoot默认记录日志的框架。 SpringBoot工程自带logback和slf4j的依赖,所以重点放在编写配置文件上,需要引入什么依赖,日志依赖冲突统统都不需要我们管了。

全面讲解logback的使用和源码:https://mp.weixin.qq.com/s/3HVbJRr2SVDf4jrGUgaWpg

Logback分为三个模块:

  • logback-core:核心代码模块,其他两个模块的核心功能和api。
  • logback-classic:对Log4j的一个改良版本,同时实现了slf4j的接口,因此您可以轻松地在logback和其他日志框架(如log4j或java.util.logging)之间来回切换。
  • logback-access:与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。

3.1.配置说明

logback的使用和logback.xml详解

3.2.logback格式化符号说明

%m:输出代码中指定的消息
%p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r:输出自应用启动到输出该log信息耗费的毫秒数
%c:输出所属的类目,通常就是所在类的全名
%t:输出产生该日志事件的线程名
%n:输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"
%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH\:mm\:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%l:输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlogback.main(TestLogback.java:10)   

3.3.logback 高级特性异步输出日志

之前的日志配置方式是基于同步的,每次日志输出到文件都会进行一次磁盘IO。采用异步写日志的方式而不让此次写日志发生磁盘IO, 阻塞线程从而造成不必要的性能损耗。异步输出日志的方式很简单,添加一个基于异步写日志的appender,并指向原先配置的appender即可

<!-- 异步输出 -->
<appender name="ASYNC-INFO" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
    <discardingThreshold>0</discardingThreshold>
    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
    <queueSize>256</queueSize>
    <!-- 添加附加的appender,最多只能添加一个 -->
    <appender-ref ref="INFO-LOG"/>
</appender>

<appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
    <discardingThreshold>0</discardingThreshold>
    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
    <queueSize>256</queueSize>
    <!-- 添加附加的appender,最多只能添加一个 -->
    <appender-ref ref="ERROR-LOG"/>
</appender>

4.slf4j

  • 官网https://www.slf4j.org/
  • 框架整合与实现原理

  • 使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
  • 实现方式统一使用: Logback、log4j、log4j2、jcl(java commons logging)、jul(jdk日志)

4.1.依赖关系

不同的日志框架之间,依赖的slf4j不同。

1. slf4j 核心包
<!--slf4j core 使用slf4j必須添加, Logback 只引入这个就行了-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.27</version>
</dependency>

2. 以下为各种日志框架的 绑定包:指定一个日志实现
<!--slf4j 自带的简单日志实现 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.27</version>
</dependency>

<!--log4j-1.2.x 适配包。即面向 slf4j 调用 API,然后它内部调用适配包,最后适配包调用 log4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.27</version>
</dependency>

<!--log4j-2.x 提供的与slf4j桥接的包 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.18.0</version>
</dependency>

<!-- 使用commons logging的依赖。引入次依赖后,原本项目中的使用JCL的日志都会重定向到slf4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jcl</artifactId>
     <version>1.7.27</version>
</dependency>

<!-- jdk 日志的依赖 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.27</version>
</dependency>

3.以下为各种各种框架的桥接包:重定向不同的日志到slf4j
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
     <version>1.7.27</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
     <version>1.7.27</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-over-slf4j</artifactId>
     <version>1.7.27</version>
</dependency>

4.2.原理

  • 桥接:不是必须的,使用桥接,将不同日志框架的日志重定向到slf4j中。
    • 作用:项目中可能使用了很多中日志框架,日志文件配置混乱。所有可以引入slf4j对于的桥接模块,实现日志收敛,且不需要修改原本的代码。
    • 目的:桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现。
    • 迁移:使用SLF4J的桥接器替换原有的日志框架,需要做的就是删除掉原有项目中的日志框架的依赖。然后替换成SLF4J提供的桥接器。
    • 本质:桥接器的本质就是内部已经提供了对应日志框架的类,包路径也是完全相同,所有项目当中原本日志的代码不要改,只是这个类的包变成了桥接器的类。
    • 注意:
      • 桥接和绑定不能同时使用。例如:jcl-over-slf4j.jar和 slf4j-jcl.jar不能同时部署。牵着将导致JCL将日志系统的选择委托给SLF4J,后者将SLF4J将日志系统的选择委托给JCL,从而导致无限循环。
      • 所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender,Filter等对象,将无法产生效果。
  • 绑定:必须的。使用绑定,将slf4j收集到的日志,将某个日志框架作为日志的实现。

  • SLF4J通过LoggerFactory加载日志具体的实现对象。
  • LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现。
  • 在绑定具体实现的时候,通过类加载器,加载org.slf4j.impl.StaticLoggerBinder.class
  • 每个绑定的依赖中都会有一个StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载

5.log4j架构

以下主要介绍log4j。logback设计架构完全适用

log4j2 由四部分构成:

  • Logger:负责捕获日志记录,并传递给 Appender,他是日志行为的发起者。
  • Appender:负责将日志事件进行分类处理,将日志发往他应该去的目标去向,因此也可以称为 Handler。
  • Layout:Layout 负责在日志输出前决定日志的格式,因此也可以称为 Fomatter。
  • Filter:Filter 是可选的组件,每一个 Logger、Appender 甚至全局都可以配置若干个 Filter,来决定相应的组件对当前的日志时间是否关心。

5.1.Appender

Appender 是 log4j2 的核心组件之一,他用来接收 LogEvent 并按照预先的配置打印到指定的位置。

log4j2支持的Appender列表查看官网:https://logging.apache.org/log4j/2.x/manual/appenders.html

常用的 Appender

  • 【最基础】ConsoleAppender 与 FileAppender:向控制台或文件输出日志。
  • 【最常用】RollingFileAppender:对于一个线上持续工作的服务来说,持续向单个文件输出日志显然是不现实的。 所以RollingFileAppender 实现了滚动式的文件存储,他有三个策略:
    • OnStartupTriggeringPolicy:每次 JVM 启动,都滚动到新的日志文件开始记录。
    • TimeBasedTriggeringPolicy:根据日期时间进行滚动。
    • SizeBasedTriggeringPolicy:按照日志文件大小进行滚动。
  • JDBCAppender:将日志写入数据库
  • FailoverAppender:写入文件还是写入数据库都是有可能写入失败的,当失败后的处理
  • AsyncAppender:异步日志
  • FlumeAppender:将几个不同源的日志汇集、集中到一处
  • JMSQueueAppender & JMSTopicAppender:JMS 相关的日志输出。
  • RewriteAppender:对日志事件进行掩码注入。
  • RoutingAppender:允许通过规则路由日志到不同的输出地。
  • SMTPAppender:通过邮件发送日志。
  • SocketAppender:以 socket 的方式发送到远程主机。

其中重要的AsyncAppender的属性如下:

参数名 参数类型 描述
AppenderRef String 最终执行日志写入的 appender,可配置多个
blocking boolean 如果为 true 或默认值,当队列满时,阻塞等待,否则将 logEvent 放入 ErrorAppender
shutdownTimeout integer LogEvent 的最长等待时间(毫秒数),为 0 或默认值表示永远等待
bufferSize integer 队列长度,默认值为 1024
errorRef String ErrorAppender,如果不配置,则丢弃所有 ErrorLogEvent
filter Filter 用来过滤 LogEvent,只有符合条件的事件才会被处理
name String Appender 名称
ignoreExceptions boolean 需要被忽略的异常
includeLocation boolean 是否打印线程本地信息,默认为 false,如果为 true,会有一定的性能损耗
BlockingQueueFactory BlockingQueueFactory BlockingQueue 的生产工厂类

5.2.Layout

Appender 解决了日志打印到哪里的问题,而 Layout 则解决日志如何打印,也就是日志格式问题,这也就是 Layout 也被称为 Formatter 的原因。

具体使用方式,参考官方。 log4j2 也同样提供了多种多样的,用来实现打印各种格式日志的丰富 Layout:

  • PatternLayout:自定义格式化格式。
  • CSVLayout:自动使用逗号隔开字段,并生成csv文件。
  • JSONTamplateLayout:可自定义JSON模板的JSON格式
  • JSONLayout:JSON格式
  • GelfLayout:压缩格式
  • HtmlLayout:HTML页面
  • SerializedLayout:序列化其他的格式
  • XMLLayout:xml格式
  • YAMLLayout:yaml格式

用法

<Console name="STDOUT" target="SYSTEM_OUT">
 <PatternLayout pattern="%m%n"/>
</Console>

5.3.Filter

Filter 是可选的,log4j2 会在日志产生时自动调用预先配置的 Filter 的 filter 方法进行过滤,以便获得是否允许打印的标识。

是否允许打印的标识是一个 Result 类型的枚举,他的值有三种:

  • ACCEPT
  • DENY
  • NEUTRAL

常见的filter

  • BurstFilter:控制日志打印速度
  • CompositeFilter:级联 Filter。扩展多个filter
  • DynamicThresholdFilter:动态日志级别设置
  • TimeFilter:限制时间的 filter
  • MarkerFilter:过滤不同类型的日志
  • MapFilter:与 DynamicThresholdFilter 类似,MapFilter 通过配置 kv 实现 MDC.get("key") == value 的情况下进行日志打印。
  • RegexFilter:支持正则表达式的 MarkerFilter
  • ScriptFilter:允许用户编写自己的 js 或 groovy 脚本决定是否 onMatch
  • ThreadContextMapFilter:DynamicThresholdFilter 的线程上下文版本。

6.logrotate

logrotate是centos自带命令,其他linux操作系统可能需要自行安装,用来进行日志切割和定期删除,简单来说就是将某个日志文件按照时间或大小分割成多份,删除时间久远的日志。

logrotate实现日志切割和清理(清晰易懂)

使用命令,在启动命令后面追加,其实就是将控制台的日志收集起来

nohup java -jar app.jar  | nohup /usr/sbin/rotatelogs log.%Y-%m-%d 86400 480 >/dev/null 2>&1 &

7.最佳实践

7.1.基本格式

使用slf4j作为门面打印日志。

// 1.必须使用参数化信息的方式,使用[]进行参数变量隔离,这样的格式写法,可读性更好,对于排查问题更有帮助。
logger.debug("Processingtradewithid:[{}]andsymbol:[{}]",id,symbol);

// 2.对于debug日志,必须判断是否为debug级别后,才进行使用:
if(logger.isDebugEnabled()){
  logger.debug("Processingtradewithid:"+id+"symbol:"+symbol);
}

3.不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。反例(不要这么做):
logger.debug("Processingtradewithid:"+id+"symbol:"+symbol);

7.2.Spring Boot的日志集成

Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容。

  • 格式化日志:
  • 多彩输出:设置spring.output.ansi.enabled参数来支持
    • NEVER:禁用ANSI-colored输出(默认项)
    • DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)
    • ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用
  • 文件输出:配置logging.file或logging.path属性。
    • logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
    • logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log
  • 级别控制:设置logging.level.*=LEVEL。其中*可以是root或者包、类等
  • 自定义输出格式:
    • logging.pattern.console:定义输出到控制台的样式(不支持JDK Logger)
    • logging.pattern.file:定义输出到文件的样式(不支持JDK Logger)
  • 自定义日志配置:Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml)
    • 设置日志的配置文件:logging.config=classpath:log4j2.properties
      • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
    • Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
    • Log4j2:log4j2-spring.xml, log4j2.xml
    • JDK (Java Util Logging):logging.properties

使用log4j2的maven配置

<!-- 排除原本的日志依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 引入log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

7.3.JUL(java.util.logging)

Java日志——JUL(java.util.logging)的使用及原理

JUL是JDK自带的一个日志实现,java.util.logging包下的各个类提供了原生的日志记录和输出支持。

LogManager的作用是读取配置文件和管理Logger实例。LogManager类初始化时默认使用JVM系统属性java.util.logging.manager定义的类路径来加载配置类。 使用场景:

  • 各种java web 容器
  • 各种日志框架

LogManager并不是同一个类的子类,而是不同的类。因为classloader在加载LogManager的时候,自动加载相关的配置。 例如,Tomcat中的应用使用了logj4,其实就是不同的classloader加载了不同的LogManager,正常情况下完全可以正常工作。 但是一个应用中,同时使用多个日志框架,因为classloader是同一个,所有会加载不同的LogManager以及配置文件,这样就会启动失败。

# jboss
-Djava.util.logging.manager=org.jboss.logmanager.LogManager
# Tomcat
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
# log4j
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager

8.日志治理&日志规范

8.1.日志治理

日志是日常开发中最有可能被忽视,最容易被滥用的一个模块。被忽视是因为打日志实在是一个再简单不过的事,被滥用是因为不同场景日志的格式内容千差万别。

日志治理的问题主要有三个方面

  1. 分类之乱
  2. 格式之乱
  3. 工具之乱

治理方式

  1. 日志分层。将日志简单分为入口,内核,出口 三层即可。异常日志单独再分离出去。
  2. 格式设计。格式尽量简单,采用约定大于配置的原则,每一个维度占据一个固定的位置,用逗号分割。切忌设计一个大而全的模型,然后直接整个的序列化为一个JSON字符串。
  3. 堆栈倒打。Java的异常堆栈,使用Caused by 进行倒序排序,导致真实异常位置是最后一个Caused,不好分析问题。最好可以控制下cause的层数
  4. 堆栈压缩。异常日志中的类包名意义不大,可以进行缩写。com.zx.Demo.run(Demo.java:100) => c.z.Demo#run:100

8.2.日志规范

8.3.日志量降低

以下5条建议并不一定适用于全部的系统,一定要具体原因具体分析,必须要先定位到造成日志量大的原因,才可以对症下药。

  1. 只打印必要的日志。
  2. 日志合并。
  3. 日志缩写、简化、压缩。
  4. 控制日志输出频率。
  5. 使用条件日志。

一.只打印必要的日志

  1. 生产环境禁止开启debug日志。
  2. 关键的信息,使用info级别的日志。过程数据尽可能使用debug级别的日志。
  3. 大对象打印,如果没有必要请谨慎打印,且只打印一次。例如请求的报文。
  4. 生产环境禁止SQL打印。如有需求,可以在请求前后,打印一些标记类的日志,比如查询的参数、查询结果返回的行数、insert和update数据后的影响行数等。
  5. 异常堆栈日志。有以下三点建议
    • 不要在逻辑判断的时候向外抛出异常。会导致日志量剧增。
    • 不要在循环的代码中使用try catch。只有出现异常就要退出循环。
    • 经常出现异常的代码,可以根据常见的出现异常的场景,提前判断处理,没有一直保留抛出异常的代码。例如JSON转换Java对象出现异常,完成可以提前判断JSON格式是否正常再进行转换。

什么是必要日志。大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。如何判断这条日志是否有必要,请考虑如下几个问题:

  1. 这些日志真的有人看吗?
  2. 看到这条日志你能做什么?
  3. 能不能给问题排查带来好处?

二.合并打印 对于一些频繁的日志输出,可以将多条日志合并为一条日志输出,避免输出过多的重复日志信息。

三.日志缩写、简化、压缩

某个日志非常有必要,但是打印的对象有些大(尤其是集合、JSON等对象),如果可以满足问题排查需求的情况下,我们可以:

  • 选择只打印其 ID
  • 创建一个只保留关键字段的日志专用对象,转化为日志专用对象,再打印。
  • 可以用缩写,如 write 简化为 w, read 简化为 r,execute 简化为 e 等;比如 pipeline 中有 20 个核心 bean ,打印日志时可以使用不同的编号替代 bean 全称,如 S1,S2 ,虽然没那么直观,但既可以查问题,又降低了日志量。

四.控制日志输出频率 通过设置日志输出间隔时间。这样可以避免在短时间内输出大量的日志信息。 例如定时任务每小时一次,只关心其中几次的场景,可以控制某个时间输出日志,其他时间不打印。

无.使用条件日志 条件日志是指只在特定条件下才输出日志。通过在代码中添加判断条件,如果条件满足则输出日志,否则不输出日志。这样可以减少不必要的日志输出。

原因定位建议:以下提供几点建议,方便定位到造成日志量太多的原因:

  1. 查看日志文件中的异常信息,判断数量比例是否较多。
  2. 分析日志频率:可以分析日志记录的频率,尤其是高频的接口和功能。如果日志记录太频繁,则会导致日志量增加。可以考虑逐渐减少日志记录的次数,直到发现有用的信息才进行记录。
  3. 使用性能分析工具来分析应用程序的运行情况,找出哪些操作或代码段可能导致了日志量增加。
  4. 确保应用程序中没有多余或冗余的日志记录,例如不必要的调试信息或重复记录。
  5. 确认日志级别是否为info,并且没有打印SQL
  6. 检查日志配置,避免重复打印日志,浪费磁盘空间,如果有单独配置logger 的话请务必在 log4j.xml 中设置 additivity=false。案例:

文章信息

张雪随笔
master
Homepage
Issues
Download

This Software is under the terms of The Unlicense.