日志框架
前言
在程序中写日志是一件非常重要,但是很容易被开发人员忽视的事情。程序中好的日志可以帮助我们大大减轻后期维护压力。在实际的工作中,开发人员往往迫于巨大时间压力,而写日志又是一个非常繁琐的事情,往往没有引起足够的重视。开发人员应在一开始就养成良好的日志撰写习惯,并且应在实际的开发工作中为写日志预留足够的时间。
一、日志简介
1、什么是日志?
简单的说,日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
2、日志的用途?
-
记录用户操作的审计日志,甚至有的时候就是监管部门的要求
-
快速定位问题的根源
-
追踪程序执行的过程
-
追踪数据的变化
-
数据统计和性能分析
-
采集运行环境数据
一般在程序上线之后,一旦发生异常,第一件事就是要弄清楚当时发生了什么。用户当时做了什么操作,环境有无影响,数据有什么变化,是不是反复发生等,然后再进一步的确定大致是哪个方面的问题。确定是程序的问题之后再交由开发人员去重现、研究、提出解决方案。这时,日志就给我们提供了第一手的资料。
二、常用的日志框架
1、日志门面
日志门面其实就是日志框架的接口,不同的日志框架可以实现同一个框架接口,在我们更换日志框架的时候只要换了jar包就行。遵循了设计原则中的依赖倒置原则。
常见的日志门面:slf4j(主流)、commons-logging
slf4j的实现框架:log4j和logback
日志级别:TRACE, DEBUG, INFO, WARN, ERROR,遵循就近原则。
- 注:我们在项目中导入包的时候一定要导入日志门面对应的接口类,不要使用实现类。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0nCr01BZ-1603758455711)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20201009095946899.png)]
2、logback(更快的执行速度使其成为主流)
推荐阅读官方文档:[](http://www.logback.cn/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%E6%9E%B6%E6%9E%84.html
2.1 引入依赖
<!--slf4j统一日志接口依赖包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!--logback依赖包-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
2.2 编写xml文件
- logback的xml文件初始化过程(寻找顺序吧)
- logback 会在类路径下寻找名为 logback-test.xml 的文件。
- 如果没有找到,logback 会继续寻找名为 logback.groovy 的文件。
- 如果没有找到,logback 会继续寻找名为 logback.xml 的文件。
- 如果没有找到,将会通过 JDK 提供的 ServiceLoader 工具在类路径下寻找文件 META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了
Configurator
接口的实现类的全限定类名。 - 如果以上都没有成功,logback 会通过 BasicConfigurator 为自己进行配置,并且日志将会全部在控制台打印出来。
我们一般会命名为logback.xml
<configuration debug="false">
<!--appender必须定义在logger的前面-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%n</pattern>
<!-- <outputPatternAsHeader>true</outputPatternAsHeader>-->
</encoder>
</appender>
<appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM INFO [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
<!--输出到指定文件-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>test.log</file>
<immediateFlush>true</immediateFlush>
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
<!--按日期输出到指定文件(基于时间的轮转策略)-->
<!-- <appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
<!-- <!– <file>testROll.log</file>–>-->
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">-->
<!-- <fileNamePattern>log/logFile.%d{yyyy-MM-dd}.log</fileNamePattern>-->
<!--保存30填记录,最大保存日志为3G-->
<!-- <maxHistory>30</maxHistory>-->
<!-- <totalSizeCap>3GB</totalSizeCap>-->
<!-- </rollingPolicy>-->
<!-- <encoder> -->
<!-- <pattern>IM ROLL [%p]%d %c: %m%n</pattern>-->
<!-- </encoder>-->
<!-- </appender>-->
<!--按日期输出到指定文件(基于时间及大小的轮转策略)-->
<!-- <appender name="ROLL_SIZEANDTIME" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
<!-- <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>-->
<!--每个文件最大100M-->
<!-- <maxFileSize>100MB</maxFileSize>-->
<!-- <maxHistory>60</maxHistory>-->
<!-- <totalSizeCap>20GB</totalSizeCap>-->
<!-- </rollingPolicy>-->
<!-- <encoder> -->
<!-- <pattern>IM ROLL_SIZEANDTIME [%p]%d %c: %m%n</pattern>-->
<!-- </encoder>-->
<!-- </appender>-->
<!--输出到数据库-->
<!-- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">-->
<!-- <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">-->
<!-- <dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">-->
<!-- <serverName>cdb-6j6jktxk.bj.tencentcdb.com</serverName>-->
<!-- <port>10175</port>-->
<!-- <databaseName>logging</databaseName>-->
<!-- <user>root</user>-->
<!-- <password>@bai880607</password>-->
<!-- </dataSource>-->
<!-- </connectionSource>-->
<!-- </appender>-->
<!--帅选策略-->
<!-- <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">-->
<!-- 在缺少 class 属性的情况下,默认的 discriminator 类型为ch.qos.logback.classic.sift.MDCBasedDiscriminator -->
<!-- <discriminator>-->
<!-- <key>userid</key>-->
<!-- <defaultValue>unknown</defaultValue>-->
<!-- </discriminator>-->
<!-- <sift>-->
<!-- <appender name="FILE-${userid}"-->
<!-- class="ch.qos.logback.core.FileAppender">-->
<!-- <file>${userid}_${FILE_NAME}.log</file>-->
<!-- <append>false</append>-->
<!-- <layout class="ch.qos.logback.classic.PatternLayout">-->
<!-- <pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</pattern>-->
<!-- </layout>-->
<!-- </appender>-->
<!-- </sift>-->
<!-- </appender>-->
<!--ROOT顶级logger-->
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
<!--自定义子logger-my_info-->
<logger name="my_info" level="INFO" additivity="false">
<appender-ref ref="my_info"/>
</logger>
<!--自定义子logger-my_info-->
<logger name="my_warn" level="DEBUG">
<appender-ref ref="FILE"/>
</logger>
</configuration>
2.3 测试用例
public class Log4jTest{
// 创建不同的logger
private static final Logger logger = LoggerFactory.getLogger(Log4jTest.class);
private static final Logger my_info = LoggerFactory.getLogger("my_info");
private static final Logger my_warn = LoggerFactory.getLogger("my_warn");
private static final Logger my_error = LoggerFactory.getLogger("my_error");
java.util.logging.Logger log = java.util.logging.Logger.getLogger(this.getClass().getName());
@Test
public void test1() {
int count = 10;
// my_warn.info("日志输出次数:{}",count);
my_warn.debug("日志输出次数:{}", count);
// MDC.put("userid", "Alice");
// logger.debug("Alice says hello");
// 打印内部的状态
// LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
// StatusPrinter.print(lc);
}
2.4组件详解
常用的2大组件looger和appender,
2.4.1 appender
appender可以控制日志输出的路径和输出格式。
日志输出路径:控制台、本地文件、远程文件、数据库。
encoder组件用来控制输出格式。(不建议使用layout)
这里只介绍3种:
1、ConsoleAppender:输出到控制台
<appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM INFO [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
2、FileAppender:输出到文件
<!--输出到指定文件-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>test.log</file>
<immediateFlush>true</immediateFlush>
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
3、RollingFileAppender:轮转输出到文件
- 基于时间的轮转策略
<!--按日期输出到指定文件(基于时间的轮转策略)-->
<appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--指定当前活动的文件名称-->
<file>testROll.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--指定归档的文件名称-->
<fileNamePattern>log/logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!--保存30天记录,最大保存日志为3G-->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>IM ROLL [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
- 基于时间和大小的轮转策略
<!--按日期输出到指定文件(基于时间及大小的轮转策略)-->
<appender name="ROLL_SIZEANDTIME" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!--每个文件最大100M-->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>IM ROLL_SIZEANDTIME [%p]%d %c: %m%n</pattern>
</encoder>
</appender>
4、DBAppender:输出到数据库
<!--输出到数据库-->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
<dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<serverName>数据库url</serverName>
<port>10175</port>
<databaseName>logging</databaseName>
<user>root</user>
<password>********</password>
</dataSource>
</connectionSource>
</appender>
- 需要建立的表及字段
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;
BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
2.4.2 logger
1、logger就是我们代码中创建的logger对象,可以控制输出级别 、引用appender和是否继承父类logger的appender。
private static final Logger logger = LoggerFactory.getLogger(Log4jTest.class);
private static final Logger my_info = LoggerFactory.getLogger("my_info");
private static final Logger my_warn = LoggerFactory.getLogger("my_warn");
private static final Logger my_error = LoggerFactory.getLogger("my_error");
2、一个logger可以引用多个appender,通过符号 . 来区分
<!--自定义子logger-my_info.my_warn-->
<logger name="my_info.my_warn" level="DEBUG">
<appender-ref ref="STDOUT"/>
</logger>
3、所有logger都默认继承root对象
<!--ROOT顶级logger-->
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
不设置也行,root对象有默认属性
4、logger的名字可以自定义,一般我们通过当前类来获取logger
5、可以通过包名或类名来指定输出级别
<!--通过包来控制输出级别-->
<logger name="com.smilevers" level="DEBUG">
</logger>
3、log4j
Log4j 是 Apache 的一个开源日志框架,也是市场占有率最多的一个框架。大多数没用过 Java Logging, 但没人敢说没用过 Log4j 吧,反正从我接触 Java 开始就是这种情况,做 Java 项目必有 Log4j 日志框架。
注意:log4j 在 2015/08/05 这一天被 Apache 宣布停止维护了,用户需要切换到 Log4j2上面去。
#ERROR To CONSOLE
log4j.rootLogger=ERROR,CONSOLE
###################
# Console Appender
###################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_root =INFO,CONSOLE
#log4j.logger.mdp_root =INFO,mdp_root
log4j.appender.mdp_root=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_root.File=${catalina.base}/logs/mdp/mdp_root.log
log4j.appender.mdp_root.Append=true
log4j.appender.mdp_root.MaxFileSize=50000KB
log4j.appender.mdp_root.MaxBackupIndex=100
log4j.appender.mdp_root.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_root.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_system =INFO,CONSOLE
#log4j.logger.mdp_system =INFO,mdp_system
log4j.appender.mdp_system=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_system.File=${catalina.base}/logs/mdp/mdp_system.log
log4j.appender.mdp_system.Append=true
log4j.appender.mdp_system.MaxFileSize=50000KB
log4j.appender.mdp_system.MaxBackupIndex=100
log4j.appender.mdp_system.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_system.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_exception =INFO,CONSOLE
#log4j.logger.mdp_exception =INFO,mdp_exception
log4j.appender.mdp_exception=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_exception.File=${catalina.base}/logs/mdp/mdp_exception.log
log4j.appender.mdp_exception.Append=true
log4j.appender.mdp_exception.MaxFileSize=50000KB
log4j.appender.mdp_exception.MaxBackupIndex=100
log4j.appender.mdp_exception.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_exception.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_debug =INFO,CONSOLE
#log4j.logger.mdp_debug =INFO,mdp_debug
log4j.appender.mdp_debug=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_debug.File=${catalina.base}/logs/mdp/mdp_debug.log
log4j.appender.mdp_debug.Append=true
log4j.appender.mdp_debug.MaxFileSize=50000KB
log4j.appender.mdp_debug.MaxBackupIndex=100
log4j.appender.mdp_debug.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_debug.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_handle =INFO,CONSOLE
#log4j.logger.mdp_handle =INFO,mdp_handle
log4j.appender.mdp_handle=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_handle.File=${catalina.base}/logs/mdp/mdp_handle.log
log4j.appender.mdp_handle.Append=true
log4j.appender.mdp_handle.MaxFileSize=50000KB
log4j.appender.mdp_handle.MaxBackupIndex=100
log4j.appender.mdp_handle.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_handle.layout.ConversionPattern=[%p] %d %c - %m%n
log4j.logger.mdp_info =INFO,CONSOLE
#log4j.logger.mdp_info =INFO,mdp_info
log4j.additivity.mdp_info= false
log4j.appender.mdp_info=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_info.File=${catalina.base}/logs/mdp/mdp_info.log
log4j.appender.mdp_info.Append=true
log4j.appender.mdp_info.MaxFileSize=50000KB
log4j.appender.mdp_info.MaxBackupIndex=100
log4j.appender.mdp_info.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_info.layout.ConversionPattern=[%p] %d %c - %m%n
#framework
log4j.logger.org.springframework=INFO
#log4j.logger.org.springframework.web=TRACE
log4j.logger.com.midea=DEBUG
log4j.logger.com.cttq=DEBUG
log4j.logger.org.apache.ibatis=DEBUG
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.com.alibaba.dubbo=ERROR
三、项目实战
1、项目日志打印2次的问题
在使用项目工具LogUtils工具打印日志的时候,会出现控制台打印2次日志的问题。
分析:日志打印2次,应该是继承的父类的appender,那我们只要添加additivity属性,并将其值设置为false即可。
2、通过类创建的logger对象打印不出日志
分析:日志打印不出,一般是级别设置的不对,通过查看配置文件可以看到,配置中指定了具体包的日志级别为DEBUG,而我们打印日志的包并没有指定,就会继承root对象的级别为ERROR,因此我们使用logger.debbug或logger.info都不能打印输出日志。我们再将所在包添加即可。
3、按级别分文件输出
<configuration>
<property name="APP_NAME" value="APPNAME"/>
<property name="LOG_HOME" value="./log/APPNAME" />
<contextName>${APP_NAME}</contextName>
<!-- 控制台日志输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss} %5p %c:%L %m%n</pattern>
</encoder>
</appender>
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 指定日志文件的名称 -->
<file>${LOG_HOME}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<MaxHistory>180</MaxHistory>
<!--
当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
<maxFileSize>30MB</maxFileSize>
</rollingPolicy>
<!--
日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
</layout>
</appender>
<!-- root是所有logger的父级,当logger的additivity=false时,logger管理的日志不再父级传递 -->
<!--root的level尽量高一些,logger的级别必须低于root的级别才能输出-->
<!--ALL > TRACE > FATAL > DEBUG > INFO > WARN > ERROR > OFF-->
<root level="DEBUG">
<appender-ref ref="stdout"/>
<appender-ref ref="FILE"/>
</root>
<!--appender和logger一起使用-->
<!--appender的name可以随意指定,被logger的appender-ref标签引用-->
<appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 指定日志文件的名称 -->
<file>${LOG_HOME}/${APP_NAME}-error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--只匹配ERROR级别日志-->-->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<MaxHistory>180</MaxHistory>
<!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
<maxFileSize>30MB</maxFileSize>
</rollingPolicy>
<!--
日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
</layout>
</appender>
<appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 指定日志文件的名称 -->
<file>${LOG_HOME}/${APP_NAME}-info.log</file>
<!--只输出INFO-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--只匹配INFO级别日志-->
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-info-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<MaxHistory>180</MaxHistory>
<!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
<maxFileSize>30MB</maxFileSize>
</rollingPolicy>
<!--
日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
</layout>
</appender>
<!--logger的name作为匹配包名的作用-->
<!--当java代码中LoggerFactory.getLogger(XX.class);匹配该XX的类名-->
<!--当java代码中LoggerFactory.getLogger("loggername");时,name属性直接写loggername-->
<logger name="com.example" additivity="false">
<appender-ref ref="infoAppender"/>
<appender-ref ref="errorAppender"/>
</logger>
</configuration>
4、 日志框架的迁移
log4j和logback可以无缝切换,不需要改变任何代码,只需要把对应的jar包换掉即可。
四、日志规范
1、Logger对象的声明和初始化
//推荐,注意导入的包一定是slf4j的
private static final Logger logger = LoggerFactory.getLogger(Xxx.class);
2、 不得使用System.out, System.err进行日志记录,请改使用logger.debug、logger.error
3、 使用参数化形式{}
占位
LOG.debug("Save order with order no:{}, and order amount:{}");
为使用占位符的,必须使用isXxxEnabled() 判断
if (logger.isDebugEnabled()) {
logger.debug(test());
}
4、 输出不同级别的日志
-
ERROR(错误)
一般用来记录程序中发生的任何异常错误信息(Throwable),或者是记录业务逻辑出错。
-
WARN(警告)
一般用来记录一些用户输入参数错误
-
INFO(信息)
默认的日志级别,用来记录程序运行中的一些有用的信息。如程序运行开始、结束、耗时、重要参数等信息,需要注意有选择性的有意义的输出,到时候自己找问题看一堆日志却找不到关键日志就没意义了
-
DEBUG(调试)
这个级别一般记录一些运行中的中间参数信息,只允许在测试环境开启,选择性在开发环境开启。
5、没有输出全部错误信息
看以下代码,这样不会记录详细的堆栈异常信息,只会记录错误基本描述信息,不利于排查问题。
try {
// ...
} catch (Exception e) {
// 错误
LOG.error('XX 发生异常', e.getMessage());
// 正确
LOG.error('XX 发生异常', e);
6、不要在千层循环中打印日志
这个是什么意思,如果你的框架使用了性能不高的 Log4j
框架,那就不要在上千个 for
循环中打印日志,这样可能会拖垮你的应用程序,如果你的程序响应时间变慢,那要考虑是不是日志打印的过多了
// 错误
for(int i=0; i<2000; i++){
LOG.info("XX");
}
最好的办法是在循环中记录要点,在循环外面总结打印出来。
7、 什么时候输出日志
7.1 异常捕获处
在捕获异常处输出日志,大家在基本都能做到,唯一需要注意的是怎么输出一个简单明了的日志信息。这在后面的问题问题中有进一步说明。
7.2 调用外部接口处
在调用外部系统时,我们需要输出日志记录返回的结果。
7.3 关键操作
关键操作的日志一般是INFO级别,如果数量、频度很高,可以考虑使用DEBUG级别。以下是一些关键操作的举例,实际的关键操作肯定不止这么多。
n 删除:删除一个文件、删除一组重要数据库记录……
n 添加:和外系统交互时,收到了一个文件、收到了一个任务……
n 处理:开始、结束一条任务……
n ……
五、日志分析、存储、展示
1、ELK简介
ELK是一套完整的日志解决方案,由ElasticSearch、Logstash、 Kibana
这三款开源软件组成。
- EastiSearch是基于Lucene开发的分布式存储检引擎,用来存储各类日志;
- Logstash对日志进行收集、分析,并将其存储供以后使用:
- Kibana 是基于Node.js开发的展示工具,为Logstah和ElasticSearch提供用于日志展示的Web界面,还用于帮助汇总、分析和搜索重要日志数据。
ELK官网https://www.elastic.co/products 分别提供包进行下载安装。
2、ELK工作原理
- 在所有需要收集日志的服务上部署Logstash,作为署Logstash agent用于监控并过滤所收集的日志,将过滤后的内容整合在一起,最终全部交给EastiSearch检索引擎;
- 用EastiSearch进行自定义检索;
- 再通过Kibana通过结合自定义检索内容生成图表,进行日志数据展示。