1. 作用
Logback将写日志事件的任务委托给appender组件完成,SiftingAppender顾名思义就是筛选日志事件,具体点就是:
对于Logback委托给它的日志事件,SiftingAppender会对日志事件做一些区分,然后不同的事件SiftingAppender会委托不同的appender去完成真正的写操作。
设想一下这样一个场景:
有一个Task类完成成一些特定的任务,每一个Task实例都有一个编号,运行时期会不停的输出任务状态的日志,下面这样:
class MyTask{
private static final Logger LOG = LoggerFactory.getLogger(MyTask.class);
private String taskId;
...
public void run(){
LOG.info("preProcess taskId={}", taskId);
preProcess();
LOG.info("process taskId={}", taskId);
process();
LOG.info("postProcess taskId={}", taskId);
postProcess();
}
}
在我们需要查看任务日志时,如果使用RollingFileAppender
, 不同taskid的任务日志都会输出到同一个文件中,那就需要打开日志文件,根据taskid查找。有时需要web application提供根据taskId查询任务日志的功能在这种情况下比较难以实现。
显然如果一个taskId生成一个日志文件就好了,等到task结束时关闭这个日志文件就行了,那以后根据taskId查询task日志时直接到指定路径下寻找文件就taskId的文件就行了。
SiftingAppender就能够实现这样的功能,他能根据一定的规则鉴别日志事件,然后委托给不同的appender完成真正的日志输出任务。
2. 使用SiftingAppender
logback中配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<!--discriminator鉴别器,根据taskId这个key对应的value鉴别日志事件,然后委托给具体appender写日志-->
<discriminator>
<key>taskId</key>
<defaultValue>default</defaultValue>
</discriminator>
<sift>
<!--具体的写日志appender,每一个taskId创建一个文件-->
<appender name="File-${taskId}" class="ch.qos.logback.core.FileAppender">
<file>/Users/eric/logbacktest/${taskId}</file>
<append>true</append>
<encoder charset="UTF-8">
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
<logger name="test.logback" level="INFO">
<appender-ref ref="SIFT"/>
</logger>
</configuration>
代码中使用日志:
package test.logback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
// 任务类
public class MyTask implements Runnable{
private static Logger LOG = LoggerFactory.getLogger(MyTask.class);
private String taskId;
public MyTask(String taskId){
this.taskId = taskId;
}
public void run() {
try{
/* 上面logback.xml中discriminator根据taskId这个key的value来决定,taskId的value通过这种方式设置,
这里设置的key-value对是保存在一个ThreadLocal<Map>中的,所以不会对其他线程中的taskId这个key产生影响
*/
MDC.put("taskId", taskId);
for(;;){
// 写日志,使用SiftingAppender,由于当前调用线程taskId的value是对应this.taskId(假设是task-0), 所以会输出到File-task-0这个文件中
LOG.info("taskId={}, threadNo={}", taskId, Thread.currentThread());
Thread.sleep(2000);
}
}catch (Exception e){
} finally{
MDC.remove(taskId);
}
}
}
-----------下面是测试类
public class SiftingAppenderTest {
public static void main(String[] args){
ExecutorService taskExecutors = Executors.newCachedThreadPool();
// 运行10个task,启动了10个线程
for(int i = 0; i < 10; ++ i){
taskExecutors.submit(new MyTask("task-" + i));
}
taskExecutors.shutdown();
}
}
运行后产生了10个日志文件如下:
日志文件.png
2.1 关于discriminator
前面配置SiftingAppender使用如下方式配置discriminator:
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<!--discriminator鉴别器,根据taskId这个key对应的value鉴别日志事件,然后委托给具体appender写日志-->
<discriminator>
<key>taskId</key>
<defaultValue>default</defaultValue>
</discriminator>
<sift>
...
</sift>
</appender>
实际上它是等同于下面这种方式的默认写法:
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
<key>taskId</key>
<defaultValue>default</defaultValue>
</discriminator>
<sift>
...
</sift>
</appender>
discriminator不指定具体class时,默认使用MDCBasedDiscriminator
,这就是我们能在前面的java代码中使用MDC.put("taskId", taskId);
来指定taskId值的原因,因为MDCBasedDiscriminator
会使用<key></key>中配置的key去到MDC中取当前线程的value,然后选择(没有就创建)appender写日志。
Discriminator接口
discriminator都是从该接口实现的,接口方法如下:
public interface Discriminator<E> extends LifeCycle {
// 这里范型E一般是日志事件,在logback-classic 中应该是ILoggingEvent,
// 在logback-access 中是AccessLogEvent
String getDiscriminatingValue(E var1);
String getKey();
}
下面是MDCBasedDiscriminator的实现:
public class MDCBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> {
// 之前的java实例代码中通过<key>taskId</key>设置这里的key是“taskId”
private String key;
private String defaultValue;
public MDCBasedDiscriminator() {
}
// event即日志事件,这个方法的返回值决定了此日志event后面会有那一个appender处理
public String getDiscriminatingValue(ILoggingEvent event) {
// 前面说过MDC.put会保存到一个ThreadLocal<Map>中去,这里拿到这个map
Map mdcMap = event.getMDCPropertyMap();
if(mdcMap == null) {
return this.defaultValue;
} else {
// 这里拿到当前线程通过MDC.put设置的key的value值
String mdcValue = (String)mdcMap.get(this.key);
return mdcValue == null?this.defaultValue:mdcValue;
}
}
public void start() {
int errors = 0;
if(OptionHelper.isEmpty(this.key)) {
++errors;
this.addError("The \"Key\" property must be set");
}
if(OptionHelper.isEmpty(this.defaultValue)) {
++errors;
this.addError("The \"DefaultValue\" property must be set");
}
if(errors == 0) {
this.started = true;
}
}
public String getKey() {
return this.key;
}
public void setKey(String key) {
this.key = key;
}
public String getDefaultValue() {
return this.defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
}
本文参考
网友评论