美文网首页
Logback - SiftingAppender

Logback - SiftingAppender

作者: aaron1993 | 来源:发表于2017-11-07 23:34 被阅读0次

    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;
        }
    }
    
    

    本文参考

    1. Logback Appenders
    2. Logback MDC

    相关文章

      网友评论

          本文标题:Logback - SiftingAppender

          本文链接:https://www.haomeiwen.com/subject/iuhjmxtx.html