logback通过SiftingAppender筛选日志事件

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Logback 是一个 Java 的日志框架,可以用来控制日志的输出。它支持多种日志级别,可以通过配置文件来控制日志的输出方式。 Logback 的配置文件是一个 XML 文件,通常命名为 `logback.xml`。下面是一个简单的 Logback 配置文件的例子: ```xml <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration> ``` 这个配置文件定义了一个名为 `STDOUT` 的输出器,它将日志输出到控制台。`<encoder>` 元素定义了日志输出的格式,`%-5level` 表示日志级别,`%logger{35}` 表示日志记录器的名称,`%msg%n` 表示日志消息和换行符。 `<root>` 元素定义了日志的根级别,这里设置为 `debug`,表示输出所有级别的日志。`<appender-ref>` 元素将输出器 `STDOUT` 添加到根记录器中。 Logback 支持多种日志级别,从低到高分别是 TRACE、DEBUG、INFO、WARN、ERROR 和 FATAL。可以通过设置日志级别来控制日志的输出。例如,如果只想输出 WARN 级别以上的日志,可以将根级别设置为 WARN: ```xml <root level="warn"> <appender-ref ref="STDOUT" /> </root> ``` 这样,INFO 和 DEBUG 级别的日志就不会被输出了。 除了根级别外,还可以为每个记录器单独设置日志级别。例如,可以将某个记录器的级别设置为 DEBUG,而其他记录器的级别设置为 INFO: ```xml <logger name="com.example" level="debug" /> ``` 这样,名为 `com.example` 的记录器就会输出 DEBUG 级别的日志Logback 还支持多种输出方式,例如文件、邮件等。可以通过配置文件来定义这些输出方式。详细的配置方法可以参考 Logback 的官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值