在程序中写日志是一件非常重要,但是很容易被开发人员忽视的事情。程序中好的日志可以帮助我们大大减轻后期维护压力。在实际的工作中,开发人员往往迫于巨大时间压力,而写日志又是一个非常繁琐的事情,往往没有引起足够的重视。开发人员应在一开始就养成良好的日志撰写习惯,并且应在实际的开发工作中为写日志预留足够的时间。
简单的说,日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
记录用户操作的审计日志,甚至有的时候就是监管部门的要求
快速定位问题的根源
追踪程序执行的过程
追踪数据的变化
数据统计和性能分析
采集运行环境数据
一般在程序上线之后,一旦发生异常,第一件事就是要弄清楚当时发生了什么。用户当时做了什么操作,环境有无影响,数据有什么变化,是不是反复发生等,然后再进一步的确定大致是哪个方面的问题。确定是程序的问题之后再交由开发人员去重现、研究、提出解决方案。这时,日志就给我们提供了第一手的资料。
日志门面其实就是日志框架的接口,不同的日志框架可以实现同一个框架接口,在我们更换日志框架的时候只要换了jar包就行。遵循了设计原则中的依赖倒置原则。
常见的日志门面:slf4j(主流)、commons-logging
slf4j的实现框架:log4j和logback
日志级别:TRACE, DEBUG, INFO, WARN, ERROR,遵循就近原则。
推荐阅读官方文档:[](http://www.logback.cn/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%E6%9E%B6%E6%9E%84.html
<!--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>
Configurator
接口的实现类的全限定类名。我们一般会命名为logback.xml
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%npattern>
encoder>
appender>
<appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM INFO [%p]%d %c: %m%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>test.logfile>
<immediateFlush>trueimmediateFlush>
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%npattern>
encoder>
appender>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
root>
<logger name="my_info" level="INFO" additivity="false">
<appender-ref ref="my_info"/>
logger>
<logger name="my_warn" level="DEBUG">
<appender-ref ref="FILE"/>
logger>
configuration>
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大组件looger和appender,
appender可以控制日志输出的路径和输出格式。
日志输出路径:控制台、本地文件、远程文件、数据库。
encoder组件用来控制输出格式。(不建议使用layout)
这里只介绍3种:
<appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>IM INFO [%p]%d %c: %m%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>test.logfile>
<immediateFlush>trueimmediateFlush>
<encoder>
<pattern>IM ROOT [%p]%d %c: %m%npattern>
encoder>
appender>
<appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>testROll.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/logFile.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>30maxHistory>
<totalSizeCap>3GBtotalSizeCap>
rollingPolicy>
<encoder>
<pattern>IM ROLL [%p]%d %c: %m%npattern>
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.txtfileNamePattern>
<maxFileSize>100MBmaxFileSize>
<maxHistory>60maxHistory>
<totalSizeCap>20GBtotalSizeCap>
rollingPolicy>
<encoder>
<pattern>IM ROLL_SIZEANDTIME [%p]%d %c: %m%npattern>
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>数据库urlserverName>
<port>10175port>
<databaseName>loggingdatabaseName>
<user>rootuser>
<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;
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 name="my_info.my_warn" level="DEBUG">
<appender-ref ref="STDOUT"/>
logger>
3、所有logger都默认继承root对象
<root level="ERROR">
<appender-ref ref="STDOUT"/>
root>
不设置也行,root对象有默认属性
4、logger的名字可以自定义,一般我们通过当前类来获取logger
5、可以通过包名或类名来指定输出级别
<logger name="com.smilevers" level="DEBUG">
logger>
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
在使用项目工具LogUtils工具打印日志的时候,会出现控制台打印2次日志的问题。
分析:日志打印2次,应该是继承的父类的appender,那我们只要添加additivity属性,并将其值设置为false即可。
分析:日志打印不出,一般是级别设置的不对,通过查看配置文件可以看到,配置中指定了具体包的日志级别为DEBUG,而我们打印日志的包并没有指定,就会继承root对象的级别为ERROR,因此我们使用logger.debbug或logger.info都不能打印输出日志。我们再将所在包添加即可。
<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%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-%d{yyyy-MM-dd}-%i.logfileNamePattern>
<MaxHistory>180MaxHistory>
<maxFileSize>30MBmaxFileSize>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
layout>
appender>
<root level="DEBUG">
<appender-ref ref="stdout"/>
<appender-ref ref="FILE"/>
root>
<appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}-error.logfile>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
-->
<level>ERRORlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.logfileNamePattern>
<MaxHistory>180MaxHistory>
<maxFileSize>30MBmaxFileSize>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
layout>
appender>
<appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}-info.logfile>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFOlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-info-%d{yyyy-MM-dd}-%i.logfileNamePattern>
<MaxHistory>180MaxHistory>
<maxFileSize>30MBmaxFileSize>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
layout>
appender>
<logger name="com.example" additivity="false">
<appender-ref ref="infoAppender"/>
<appender-ref ref="errorAppender"/>
logger>
configuration>
log4j和logback可以无缝切换,不需要改变任何代码,只需要把对应的jar包换掉即可。
//推荐,注意导入的包一定是slf4j的
private static final Logger logger = LoggerFactory.getLogger(Xxx.class);
{}
占位LOG.debug("Save order with order no:{}, and order amount:{}");
为使用占位符的,必须使用isXxxEnabled() 判断
if (logger.isDebugEnabled()) {
logger.debug(test());
}
ERROR(错误)
一般用来记录程序中发生的任何异常错误信息(Throwable),或者是记录业务逻辑出错。
WARN(警告)
一般用来记录一些用户输入参数错误
INFO(信息)
默认的日志级别,用来记录程序运行中的一些有用的信息。如程序运行开始、结束、耗时、重要参数等信息,需要注意有选择性的有意义的输出,到时候自己找问题看一堆日志却找不到关键日志就没意义了
DEBUG(调试)
这个级别一般记录一些运行中的中间参数信息,只允许在测试环境开启,选择性在开发环境开启。
看以下代码,这样不会记录详细的堆栈异常信息,只会记录错误基本描述信息,不利于排查问题。
try {
// ...
} catch (Exception e) {
// 错误
LOG.error('XX 发生异常', e.getMessage());
// 正确
LOG.error('XX 发生异常', e);
这个是什么意思,如果你的框架使用了性能不高的 Log4j
框架,那就不要在上千个 for
循环中打印日志,这样可能会拖垮你的应用程序,如果你的程序响应时间变慢,那要考虑是不是日志打印的过多了
// 错误
for(int i=0; i<2000; i++){
LOG.info("XX");
}
最好的办法是在循环中记录要点,在循环外面总结打印出来。
在捕获异常处输出日志,大家在基本都能做到,唯一需要注意的是怎么输出一个简单明了的日志信息。这在后面的问题问题中有进一步说明。
在调用外部系统时,我们需要输出日志记录返回的结果。
关键操作的日志一般是INFO级别,如果数量、频度很高,可以考虑使用DEBUG级别。以下是一些关键操作的举例,实际的关键操作肯定不止这么多。
n 删除:删除一个文件、删除一组重要数据库记录……
n 添加:和外系统交互时,收到了一个文件、收到了一个任务……
n 处理:开始、结束一条任务……
n ……
ELK是一套完整的日志解决方案,由ElasticSearch、Logstash、 Kibana
这三款开源软件组成。
ELK官网https://www.elastic.co/products 分别提供包进行下载安装。
详细参考:https://blog.51cto.com/11134648/2163789
logback官方文档