Logback - SiftingAppender

参考 https://www.jianshu.com/p/a33902d58530
参考 https://blog.csdn.net/Alex19961223/article/details/103878721 - 这篇讲得好。

配置 logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="120 seconds">

    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) %X{C} - %msg%n
            </pattern>
        </encoder>
    </appender>

    <appender name="defaultAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${HOME}/Documents/rpl-task/polarx.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${HOME}/Documents/rpl-task/%d{yyyy-MM-dd}/polarx.%i.log.gz
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>200MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>
                <![CDATA[
                    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n
                ]]>
            </pattern>
            <charset class="java.nio.charset.Charset">UTF-8</charset>
        </encoder>
    </appender>

    <appender name="commitAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
            <Key>taskName</Key>
            <DefaultValue>default</DefaultValue>
        </discriminator>
        <sift>
            <appender name="commitAppender-${taskName}"
                      class="ch.qos.logback.core.rolling.RollingFileAppender">
                <file>${user.home}/logs/rpl/${taskName}/commit.log</file>
                <append>true</append>
                <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                    <!-- rollover daily -->
                    <fileNamePattern>${user.home}/logs/rpl/${taskName}/%d{yyyy-MM-dd}/commit.%i.log.gz
                    </fileNamePattern>
                    <maxFileSize>200MB</maxFileSize>
                    <maxHistory>30</maxHistory>
                    <totalSizeCap>5GB</totalSizeCap>
                    <delayMinute>5</delayMinute>
                </rollingPolicy>
                <encoder>
                    <pattern>
                        <![CDATA[
                        %m%n
                        ]]>
                    </pattern>
                    <charset class="java.nio.charset.Charset">UTF-8</charset>
                </encoder>
            </appender>
        </sift>
    </appender>

    <appender name="checkResultAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
            <Key>taskName</Key>
            <DefaultValue>default</DefaultValue>
        </discriminator>
        <sift>
            <appender name="checkResultAppender-${taskName}"
                      class="ch.qos.logback.core.FileAppender">
                <file>${user.home}/logs/rpl/${taskName}/check.log</file>
                <append>true</append>
                <encoder>
                    <pattern>
                        <![CDATA[
                        %m%n
                        ]]>
                    </pattern>
                    <charset class="java.nio.charset.Charset">UTF-8</charset>
                </encoder>
            </appender>
        </sift>
    </appender>

    <logger name="defaultLogger" level="INFO">
        <appender-ref ref="defaultAppender"/>
    </logger>

    <logger name="consoleLogger" level="INFO">
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="commitLogger" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="commitAppender" additivity="false"/>
    </logger>

    <logger name="checkResultLogger" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="checkResultAppender" additivity="false"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="defaultAppender"/>
    </root>
</configuration>

LogUtil 代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class LogUtil {

    /**
     * Log4j is thread safe
     */

    private static String SIFT_LOGGER_KEY     = "taskName";
    private static String COMMIT_LOGGER       = "commitLogger";
    private static String CHECK_RESULT_LOGGER = "checkResultLogger";

    public static Logger getCommitLogger() {
        MDC.put(SIFT_LOGGER_KEY, TaskContext.getInstance().getTaskName());
        return LoggerFactory.getLogger(COMMIT_LOGGER);
    }

    public static Logger getCheckResultLogger() {
        MDC.put(SIFT_LOGGER_KEY, TaskContext.getInstance().getTaskName());
        return LoggerFactory.getLogger(CHECK_RESULT_LOGGER);
    }

问题排查

使用上面代码。发现:

我使用 static final Logger logger = LogUtil.getCommitLogger();

在单线程中 ,log 写入到了正确的文件(文件路径由 MDC 传入。与 TaskContext.getInstance().getTaskName() 绑定)。

但是在多线程中,log 始终写入到了 default 路径。也就是 MDC 传入的 taskName 没有生效。

经过排查:
发现创建 logger 和使用 logger 的不是同一个线程。创建 logger 时 MDC 传入的 taskName 在使用 logger 的线程中已经不起作用了,故 logger 必须在使用它的线程中创建,MDC 才能起作用。

参考:
https://blog.csdn.net/pengjunlee/article/details/88059664

以下摘自:https://blog.csdn.net/Alex19961223/article/details/103878721
MDCBasedDiscriminator 的实现,可以看出之所以是跟线程相关的,因为MDC.put会保存到一个ThreadLocal中去

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

如果想要进程级别区分的 logger

MDCBasedDiscriminator 是基于 MDC 的,如果要每个线程打出的 log 都到同一个文件夹,则需要每个线程启动时都创建一次 logger。如果整个进程都希望打 log 到同一个文件夹,则需要如下改动:

  • 创建自己的 TaskBasedDiscriminator。其实就是基于 MDCBasedDiscriminator 改动的。
  • 修改 logback.xml 中的 MDCBasedDiscriminator 为 TaskBasedDiscriminator
public class TaskBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> {
    private String key;
    private String defaultValue;
    private static Map<String, String> values = new HashMap<>();

    public TaskBasedDiscriminator() {
    }

    @Override
    public String getDiscriminatingValue(ILoggingEvent event) {
        return values.containsKey(key) ? values.get(key) : defaultValue;
    }

    @Override
    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 static void put(String key, String value) {
        values.put(key, value);
    }

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

Log4j输出格式控制–log4j的PatternLayout参数含义

https://blog.csdn.net/guoquanyou/article/details/5689652

slf4j log4j logback关系详解和相关用法

https://www.cnblogs.com/sinte-beuve/p/5758971.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值