前因

前阵子日子线上有个bug,可是由于触发过于频频导致日志打印满是这个bug的仓库,满是重复仓库内容日志一下变得不友好起来,搜索有没有什么优化办法。

偶尔看到近期业务很多突增微服务性能优化总结-2.开发日志输出反常仓库的过滤插件这篇文章,企图将同样的日志仓库过滤插件复刻到logback中

后来发现仓库打印仍是至少同样的仓库一天内至少打印一次(日志文件依照天进行拆分)才便利排查,所以就需要判别一个反常的仓库是否打印过,自然就想到了大名鼎鼎的布隆过滤器和布谷鸟过滤器,遂进行改造

思路

思路是改造ch.qos.logback.classic.pattern.ThrowableProxyConverter ,通过在logback装备中设置日志输出的PATTERNconverterClass 来取代默认的org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter

然后在isIgnoredStackTraceLine办法中加入自定义的过滤逻辑

判别是否重复运用布隆和布谷鸟过滤器

代码及演示Demo

悉数代码 GitHub:LogNoiseLess

由于有对logback类的copy改造,还涉及到spring环境变量加载和日志系统加载打印的先后联系、日志反常插件自己报反常怎样debug等一堆小坑,代码比较多,建议用的时分能够直接fork改改

这儿只看中心的两个办法: 一个是判别该行是否应该被过滤

@Override
protected boolean isIgnoredStackTraceLine(String line) {
    //...
    try {
        // 详细判别逻辑托付给接口的完成类
        return predicate.isShouldSkipLine(line);
    } catch (Exception e) {
        //...
    }
    return super.isIgnoredStackTraceLine(line);
}

一个判别该反常仓库是否第一次打印,这儿运用弱引用key Cache是由于logback对于同一个反常会屡次调用这个办法

//...
@Override
public boolean isShouldEnableSkip(IThrowableProxy throwableProxy) {
    //假如 mightContain false 则直接判别为打印完整仓库(return false)
    return Boolean.TRUE.equals(RESULT_CACHE.get().get(throwableProxy, k -> autoRebuildCuckooFilter.isFull(k.getStackTraceElementProxyArray())));
}
//...
public boolean isFull(T item) {
    //判别是否是新的周期新建过滤器
    CuckooFilter<T> cuckooFilter = rebuildIfNecessary();
    //...
    //交给布谷鸟过滤器或者布隆过滤器来判别是否打印过
    if (cuckooFilter.approximateCount(item) >= times) {
        return true;
    }
    return !cuckooFilter.put(item);
}
//...

运用及作用

如下示例logback装备

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--  导入spring根底装备  -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!-- 由于覆盖了特点,所以这儿不能直接借用  -->
<!--    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>-->
    <!--  替换defaults.xml中定义的特点, 运用自己的噪音按捺反常转换器 -->
    <conversionRule conversionWord="wEx"
                    converterClass="com.muyuanjin.lognoiseless.NoiseLessThrowableProxyConverter"/>
    <!-- 控制台日志,由于覆盖了特点,所以这儿不能直接借用 console-appender.xml ,copy一遍-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>${CONSOLE_LOG_CHARSET}</charset>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

然后在spring yaml装备文件装备: 白名单形式

logback:
  stackTrace:
    skipLine: com.example,java,org # 多个包名以逗号分隔
    skipLineMode: whitelist #白名单形式
    maxNumPerCycle: 1 # 每个周期内最大打印全栈的次数,=0时每次均越过, =1时运用 布隆过滤器
    cycleDuration: 2h #计数周期

或者黑名单形式

logback:
  stackTrace:
    skipLine: com.example,java,org # 多个包名以逗号分隔
    skipLineMode: blacklist #黑名单形式
    maxNumPerCycle: 1 # 每个周期内最大打印全栈的次数,=0时每次均越过, =1时运用 布隆过滤器
    cycleDuration: 2h #计数周期

或者 class 或 spring bean (需完成StackLineSkipPredicate)

logback:
  stackTrace:
    skipLine: com.example.TestStackLineSkipPredicate
    skipLineMode: predicate_class #谓词类
    maxNumPerCycle: 1 # 每个周期内最大打印全栈的次数,=0时每次均越过, =1时运用 布隆过滤器
    cycleDuration: 2h #计数周期

或者 janino表达式,运用 line 表明该行的字符串参数, 返回true时表明不打印改行

logback:
  stackTrace:
    skipLine: "!line.contains(\"com.example\")" #必须以com.example开头才打印
    skipLineMode: janino_expression #janino表达式
    maxNumPerCycle: 1 # 每个周期内最大打印全栈的次数,=0时每次均越过, =1时运用 布隆过滤器
    cycleDuration: 2h #计数周期

运转 demo 里的 Test 检查作用:

@Slf4j
@SpringBootTest
class LogNoiseLessDemoApplicationTests {
    @Test
    void contextLoads() {
        for (int i = 0; i < 5; i++) {
            log.warn("test", new RuntimeException("test"));
        }
    }
}

使用布谷鸟过滤器对 logback 日志一定周期内重复异常堆栈打印进行压缩过滤

OK,搞定