我們在日常工作中,會遇見鏈路過長、日志量過大、構建數(shù)據(jù)復雜、復現(xiàn)困難等痛點。我們期望有一種解決方案:可以對規(guī)則加白,命中規(guī)則的日志會打印出來,被ELK收集,但是可以盡量不影響我們的業(yè)務邏輯,可以借助日志的Filter來實現(xiàn):
實現(xiàn)方式
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class MyThresholdFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if ("com.tellme.controller.liteFlowController".equals(event.getLoggerName())) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
}
使用方式:
<property name="core.path" value="core.log"/>
<!--特定日志:輸出到文件-->
<appender name="file1" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="com.tellme.logfilter.MyThresholdFilter"/>
<file>${core.path}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>coreback.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<!--格式化輸出:%d:表示日期 %thread:表示線程名 %-5level:級別從左顯示5個字符寬度 %msg:日志消息 %n:是換行符-->
<pattern>文件記錄-%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="file1"/>
</root>
- 我們在日志收集的時候,可以收集debug級別的詳細日志;

日志對象debug.png
擴展:我們可以借助Spring的機制,動態(tài)加載Filter過濾器
src/main/resources/META-INF/spring.factories類
org.springframework.context.ApplicationListener=\
com.tellme.starter.DynamicLogTurboApplicationListener
監(jiān)聽類,
import com.tellme.logfilter.DynamicLogTurboFilter;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.ConfigurableEnvironment;
public class DynamicLogTurboApplicationListener implements GenericApplicationListener {
public static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
private static final Class<?> EVENT_TYPE = ApplicationPreparedEvent.class;
private int order = DEFAULT_ORDER;
@Override
public boolean supportsEventType(ResolvableType eventType) {
if (eventType.getRawClass() == null) {
return false;
}
return EVENT_TYPE.isAssignableFrom(eventType.getRawClass());
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
ConfigurableEnvironment environment = ((ApplicationPreparedEvent) event).getApplicationContext().getEnvironment();
DynamicLogTurboFilter.addFilterToContext(false);
}
}
加載到log工廠中:
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.TurboFilterList;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.ILoggerFactory;
import org.slf4j.Marker;
import org.slf4j.impl.StaticLoggerBinder;
public class DynamicLogTurboFilter extends AbstractTurboFilter {
private static final ThreadLocal<Boolean> LOOP_CHECK = ThreadLocal.withInitial(() -> Boolean.FALSE);
DynamicLogTurboFilter(boolean skipWriteFile) {
setName("DynamicLogTurboFilter");
setSkipWriteFile(skipWriteFile);
}
@Override
protected FilterReply doDecide(Marker marker, Logger logger, Level level, String format,
Object[] params, Throwable t) {
if ("com.tellme.controller.liteFlowController".equals(logger.getName())) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
@Override
protected void clearInLoopMark() {
LOOP_CHECK.remove();
}
@Override
protected void markInLoop() {
LOOP_CHECK.set(Boolean.TRUE);
}
@Override
protected boolean checkInLoop() {
return Boolean.TRUE.equals(LOOP_CHECK.get());
}
public static synchronized void addFilterToContext(boolean skipWriteFile) {
ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
if (loggerFactory instanceof LoggerContext) {
TurboFilterList turboFilterList = ((LoggerContext) loggerFactory).getTurboFilterList();
for (TurboFilter turboFilter : turboFilterList) {
if (turboFilter.getClass().equals(DynamicLogTurboFilter.class)) {
return;
}
}
((LoggerContext) loggerFactory).addTurboFilter(new DynamicLogTurboFilter(skipWriteFile));
}
}
}
監(jiān)聽父類:
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public abstract class AbstractTurboFilter extends TurboFilter {
private static final Marker SKIP_RATE_MARKER = MarkerFactory.getMarker("SKIP_RATE_MARKER");
private boolean skipWriteFile = false;
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
long start = System.currentTimeMillis();
if (checkInLoop()) {
return FilterReply.NEUTRAL;
}
markInLoop();
try {
// 如果跳過寫文件, 且調(diào)用方不是從 SKIP_RATE_MARKER 過來則不用走這個filter了
if (skipWriteFile && !SKIP_RATE_MARKER.equals(marker)) {
return FilterReply.NEUTRAL;
}
return doDecide(marker, logger, level, format, params, t);
} catch (Exception e) {
} finally {
clearInLoopMark();
}
return FilterReply.NEUTRAL;
}
protected abstract FilterReply doDecide(Marker marker, Logger logger, Level level, String format,
Object[] params, Throwable t);
protected abstract void clearInLoopMark();
protected abstract void markInLoop();
protected abstract boolean checkInLoop();
public AbstractTurboFilter setSkipWriteFile(boolean skipWriteFile) {
this.skipWriteFile = skipWriteFile;
return this;
}
}