实际开发中,日志对于我们定位问题,快速解决问题是非常重要的,所以好的日志输出项与日志文件有效的拆分是至关重要的。
最近新开发的一个项目就要求,按照不同日志级别、不同的类将信息写入到不同的文件,具体结构如图。这样通过自己在实际方法中定义输出日志级别,出现问题后直接进入到对应的类中看对应的日志信息即可。
这里需要在项目中引入对应的pom依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
实际想只通过在log4j.properties进行定义,是不能达到这样效果的,首先我们先看一下源码中是如何定义的:
package org.apache.log4j;
public abstract class AppenderSkeleton implements Appender, OptionHandler {
/**
大概在211行
Check whether the message level is below the appender's
threshold. If there is no threshold set, then the return value is
always <code>true</code>.
*/
public
boolean isAsSevereAsThreshold(Priority priority) {
return ((threshold == null) || priority.isGreaterOrEqual(threshold));
}
/**
* This method performs threshold checks and invokes filters before
* delegating actual logging to the subclasses specific {@link
* AppenderSkeleton#append} method.
* */
public
synchronized
void doAppend(LoggingEvent event) {
if(closed) {
LogLog.error("Attempted to append to closed appender named ["+name+"].");
return;
}
if(!isAsSevereAsThreshold(event.getLevel())) {
return;
}
Filter f = this.headFilter;
FILTER_LOOP:
while(f != null) {
switch(f.decide(event)) {
case Filter.DENY: return;
case Filter.ACCEPT: break FILTER_LOOP;
case Filter.NEUTRAL: f = f.getNext();
}
}
this.append(event);
}
}
从代码中这行代码可以看出,其文件输出是通过日志级别的高低确定输出的路径的。
return ((threshold == null) || priority.isGreaterOrEqual(threshold))
因此想实现我们的想法,需要自定义LogAppender类,继承DailyRollingFileAppender,然后重写其中的isAsSevereAsThreshold方法。具体实现如下:
/**
* Author: jjs
* Description: 按照日志级别,将日志输出到不同文件
* Version: 1.0
*/
public class LogAppender extends DailyRollingFileAppender {
@Override
public boolean isAsSevereAsThreshold(Priority priority) {
//只判断是否相等,而不判断优先级
return this.getThreshold().equals(priority);
}
}
这样在进行判断的时候,只有当Threshold和priority一致时,才进行输出,最后通过对log4j.properties进行配置,才能达到我们要的根据不同日志级别进行不同文件的输出,具体的log4j.properties配置如下:
log4j.rootLogger = info,info,warn,error
log4j.logger.info = info
# com.ccbscf.utils.LogAppender就是我们自己实现的类
log4j.appender.info = com.ccbscf.utils.LogAppender
log4j.appender.info.layout = org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern = [%p] [%-d{HH:mm:ss}] [%c{1}:%L] - %m%n
log4j.appender.info.DatePattern = '.'yyyy-MM-dd-a
log4j.appender.info.Threshold = INFO
log4j.appender.info.append = true
log4j.appender.info.File = /logs/info.log
log4j.logger.warn = warn
log4j.appender.warn = com.ccbscf.utils.LogAppender
log4j.appender.warn.layout = org.apache.log4j.PatternLayout
log4j.appender.warn.layout.ConversionPattern = [%p] [%-d{HH:mm:ss}] [%c{1}:%L] - %m%n
log4j.appender.warn.DatePattern = '.'yyyy-MM-dd-a
log4j.appender.warn.Threshold = WARN
log4j.appender.warn.append = true
log4j.appender.warn.File = /logs/warn.log
log4j.logger.error = error
log4j.appender.error = com.ccbscf.utils.LogAppender
log4j.appender.error.layout = org.apache.log4j.PatternLayout
log4j.appender.error.layout.ConversionPattern = [%p] [%-d{HH:mm:ss}] [%c{1}:%L] - %m%n
log4j.appender.error.DatePattern = '.'yyyy-MM-dd-a
log4j.appender.error.Threshold = ERROR
log4j.appender.error.append = true
log4j.appender.error.File = /logs/error.log
到这里其实只完成了一半,如上的配置只能实现按照不同级别将日志输出到不同级别的文件,但是其实我们想实现的是根据不同的类不同的日志级别输出到不同的文件中,这里我们还需要做一个关键的操作。就是自己实现将输入日志按照自己定义的路径写入到不同的文件,具体可以看每行注释。
/**
* 重写log4j日志生成路径
* @author jjs
* version 1.0
*/
public class RedirectLog {
public RedirectLog(String logLevel, String fileName){
LogAppender appender = (LogAppender) Logger.getRootLogger().getAppender(logLevel);
//动态地修改这个文件名
appender.setFile(fileName);
// log的文字码
appender.setEncoding("UTF-8");
// true:在已存在log文件后面追加 false:新log覆盖以前的log
appender.setAppend(true);
// 适用当前配置
appender.activateOptions();
}
}
在实际调用的地方需要进行创建该对象,以达到我们要求的效果:
private static final Logger logger = Logger.getLogger(MySQLConsumer.class);
new RedirectLog("error", "mysql_error.log");
logger.error(e.toString());
到此就完全实现了我们的功能,这种方式方便我们查找哪个类哪一行代码出现了问题,到底是哪一种级别的日志。
每个公司可能有自己定义好的日志输出格式,适合自己的才是最好的。