参考 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