日志
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事件的线程名
*/
######### 特殊符号 ############
#有些特殊符号不能直接打印,需要使用实体名称或者编号
//
& —— & 或者 &
< —— < 或者 <
> —— > 或者 >
" —— " 或者 "
‘ —— ' 或者 '
*/
######## 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.配置说明
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操作系统可能需要自行安装,用来进行日志切割和定期删除,简单来说就是将某个日志文件按照时间或大小分割成多份,删除时间久远的日志。
使用命令,在启动命令后面追加,其实就是将控制台的日志收集起来
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
- 设置日志的配置文件:logging.config=classpath:log4j2.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.日志治理
日志是日常开发中最有可能被忽视,最容易被滥用的一个模块。被忽视是因为打日志实在是一个再简单不过的事,被滥用是因为不同场景日志的格式内容千差万别。
日志治理的问题主要有三个方面
- 分类之乱
- 格式之乱
- 工具之乱
治理方式
- 日志分层。将日志简单分为入口,内核,出口 三层即可。异常日志单独再分离出去。
- 格式设计。格式尽量简单,采用约定大于配置的原则,每一个维度占据一个固定的位置,用逗号分割。切忌设计一个大而全的模型,然后直接整个的序列化为一个JSON字符串。
- 堆栈倒打。Java的异常堆栈,使用Caused by 进行倒序排序,导致真实异常位置是最后一个Caused,不好分析问题。最好可以控制下cause的层数
- 堆栈压缩。异常日志中的类包名意义不大,可以进行缩写。com.zx.Demo.run(Demo.java:100) => c.z.Demo#run:100
8.2.日志规范
8.3.日志量降低
以下5条建议并不一定适用于全部的系统,一定要具体原因具体分析,必须要先定位到造成日志量大的原因,才可以对症下药。
- 只打印必要的日志。
- 日志合并。
- 日志缩写、简化、压缩。
- 控制日志输出频率。
- 使用条件日志。
一.只打印必要的日志
- 生产环境禁止开启debug日志。
- 关键的信息,使用info级别的日志。过程数据尽可能使用debug级别的日志。
- 大对象打印,如果没有必要请谨慎打印,且只打印一次。例如请求的报文。
- 生产环境禁止SQL打印。如有需求,可以在请求前后,打印一些标记类的日志,比如查询的参数、查询结果返回的行数、insert和update数据后的影响行数等。
- 异常堆栈日志。有以下三点建议
- 不要在逻辑判断的时候向外抛出异常。会导致日志量剧增。
- 不要在循环的代码中使用try catch。只有出现异常就要退出循环。
- 经常出现异常的代码,可以根据常见的出现异常的场景,提前判断处理,没有一直保留抛出异常的代码。例如JSON转换Java对象出现异常,完成可以提前判断JSON格式是否正常再进行转换。
什么是必要日志。大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。如何判断这条日志是否有必要,请考虑如下几个问题:
- 这些日志真的有人看吗?
- 看到这条日志你能做什么?
- 能不能给问题排查带来好处?
二.合并打印 对于一些频繁的日志输出,可以将多条日志合并为一条日志输出,避免输出过多的重复日志信息。
三.日志缩写、简化、压缩
某个日志非常有必要,但是打印的对象有些大(尤其是集合、JSON等对象),如果可以满足问题排查需求的情况下,我们可以:
- 选择只打印其 ID
- 创建一个只保留关键字段的日志专用对象,转化为日志专用对象,再打印。
- 可以用缩写,如 write 简化为 w, read 简化为 r,execute 简化为 e 等;比如 pipeline 中有 20 个核心 bean ,打印日志时可以使用不同的编号替代 bean 全称,如 S1,S2 ,虽然没那么直观,但既可以查问题,又降低了日志量。
四.控制日志输出频率 通过设置日志输出间隔时间。这样可以避免在短时间内输出大量的日志信息。 例如定时任务每小时一次,只关心其中几次的场景,可以控制某个时间输出日志,其他时间不打印。
无.使用条件日志 条件日志是指只在特定条件下才输出日志。通过在代码中添加判断条件,如果条件满足则输出日志,否则不输出日志。这样可以减少不必要的日志输出。
原因定位建议:以下提供几点建议,方便定位到造成日志量太多的原因:
- 查看日志文件中的异常信息,判断数量比例是否较多。
- 分析日志频率:可以分析日志记录的频率,尤其是高频的接口和功能。如果日志记录太频繁,则会导致日志量增加。可以考虑逐渐减少日志记录的次数,直到发现有用的信息才进行记录。
- 使用性能分析工具来分析应用程序的运行情况,找出哪些操作或代码段可能导致了日志量增加。
- 确保应用程序中没有多余或冗余的日志记录,例如不必要的调试信息或重复记录。
- 确认日志级别是否为info,并且没有打印SQL
- 检查日志配置,避免重复打印日志,浪费磁盘空间,如果有单独配置logger 的话请务必在 log4j.xml 中设置 additivity=false。案例:
- 本文作者:zhangxue
- 本文链接:https://lucksnow1989.github.io/article/9.开源/日志.html
- 版权声明:本文为本人原创文章,遵循 CC 4.0 BY-NC-SA版权协议,转载请附上原文出处链接和本声明。