【问题标题】:How to prevent log bombing using logback?如何使用 logback 防止日志轰炸?
【发布时间】:2018-09-08 03:22:36
【问题描述】:

我不确定“轰炸”一词是否正确。通过轰炸,我的意思是日志发生多次,内容相同(消息和参数)。

例如,拒绝服务附加可能会导致未经身份验证的用户正在尝试访问某些 API 的日志警告。日志:

[03-29 11:26:01.000] missing token
[03-29 11:26:01.001] missing token
[03-29 11:26:01.005] missing token
... overall 100000 times

我要解决的问题是防止日志变得非常大。大小是个问题。此外,由于重复消息的数量过多,可能看不到其他重要消息。

我希望能够防止此类日志轰炸并获得一些汇总消息,例如:

[03-29 11:26:01.000] missing token
[03-29 11:26:06.000] missing token [silenced. Overall 100000 times]

所以,我在这里寻找 2 个功能:

  1. 忽略过于重复的日志。
  2. 显示消音日志的摘要。

知道是否以及如何使用 logback 来实现这一点吗?也许另一个日志工具可以支持这一点?谢谢。

【问题讨论】:

  • 您可以配置 logback 来滚动日志并压缩它,这样您就不会耗尽磁盘空间。 examples.javacodegeeks.com/enterprise-java/logback/…
  • 解决问题而不是修复日志配置怎么样?
  • @alex440 这样做的缺点:(1)大量重复消息可能会掩盖合法消息并导致它们被删除(在翻转期间被删除的文件); (2) 它允许对重复消息进行不必要的磁盘写入。
  • @VictorGubin 我同意,假设可以修改代码并且修复是可行的。最大限度地减少重复日志噪音可能是解决实际问题以确定正确修复的第一步。

标签: java logging logback slf4j


【解决方案1】:

我创建了一个通用的 DuplicateActionFilterByInsertTime 类,其中包含一个小的日志实现。

示例用法:

DuplicateLogFilter logSilencer = new DuplicateLogFilter(10000);// 10 secs filter
Logger logger = Logger.getLogger(DuplicateActionFilterByInsertTimeTest.class.getName());

logSilencer.log(new LoggerMessage(logger, Level.INFO, "Hello {0}", new Object[]{"Alik"})));

DuplicateLogFilter:

class DuplicateLogFilter extends DuplicateActionFilterByInsertTime<LoggerMessage> {
    DuplicateLogFilter(int filterMillis) {
        super(filterMillis);
        addListener(new DuplicateActionFilterByInsertTime.Listener<LoggerMessage>() {
            @Override
            public void onFilteringFinished(FilteredItem<LoggerMessage> filteredItem) {
                filteredItem.getItem().getLogger().log(Level.INFO, filteredItem.getItem().getMessage() + ". Filtered. Overall " + filteredItem.getSilenceInfo().getCount() + " messages", filteredItem.getItem().getParams());
            }

            @Override
            public void onFilteringStarted(LoggerMessage loggerMessage) {
                loggerMessage.getLogger().log(Level.INFO, loggerMessage.getMessage() + ". Filtering duplicate logs...", loggerMessage.getParams());
            }
        });

    }
    void log(LoggerMessage loggerMessage) {
        run(loggerMessage);
    }
}

实际执行所有过滤的基类

(`DuplicateActionFilterByInsertTime`:
public class DuplicateActionFilterByInsertTime<E extends Runnable> {

    private static final Logger LOGGER = Logger.getLogger(DuplicateActionFilterByInsertTime.class.getName());

    private final long filterMillis;

    private final ConcurrentHashMap<E, FilterInfoImpl> actionMap = new ConcurrentHashMap<>();

    private final ConcurrentLinkedQueue<E> actionQueue = new ConcurrentLinkedQueue<>();

    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    private final AtomicBoolean purgerRegistered = new AtomicBoolean(false);

    private final Set<Listener<E>> listeners = ConcurrentHashMap.newKeySet();

    public DuplicateActionFilterByInsertTime(int filterMillis) {
        this.filterMillis = filterMillis;
    }

    public FilterInfo get(E e) {
        FilterInfoImpl insertionData = actionMap.get(e);
        if (insertionData == null || insertionData.isExpired(filterMillis)) {
            return null;
        }
        return insertionData;
    }

    public boolean run(E e) {
        actionMap.computeIfPresent(e, (e1, insertionData) -> {
            int count = insertionData.incrementAndGet();
            if (count == 2) {
                notifyFilteringStarted(e1);
            }
            return insertionData;
        });
        boolean isNew = actionMap.computeIfAbsent(e, e1 -> {
            FilterInfoImpl insertionData = new FilterInfoImpl();
            actionQueue.add(e1);
            return insertionData;
        }).getCount() == 1;

        tryRegisterPurger();

        if (isNew) {
            e.run();
        }
        return isNew;
    }

    private void tryRegisterPurger() {
        if (actionMap.size() != 0 && purgerRegistered.compareAndSet(false, true)) {
            scheduledExecutorService.schedule(() -> {
                try {
                    for (Iterator<E> iterator = actionQueue.iterator(); iterator.hasNext(); ) {
                        E e = iterator.next();
                        FilterInfoImpl insertionData = actionMap.get(e);
                        if (insertionData == null || insertionData.isExpired(filterMillis)) {
                            iterator.remove();
                        }
                        if (insertionData != null && insertionData.isExpired(filterMillis)) {
                            FilterInfoImpl removed = actionMap.remove(e);
                            FilteredItem<E> filteredItem = new FilteredItem<>(e, removed);
                            notifyFilteringFinished(filteredItem);
                        } else {
                            // All the elements that were left shouldn't be purged.
                            break;
                        }
                    }
                } finally {
                    purgerRegistered.set(false);
                    tryRegisterPurger();
                }
            }, filterMillis, TimeUnit.MILLISECONDS);
        }
    }

    private void notifyFilteringFinished(FilteredItem<E> filteredItem) {
        new Thread(() -> listeners.forEach(l -> {
            try {
                l.onFilteringFinished(filteredItem);
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Purge notification failed. Continuing to next one (if exists)", e);
            }
        })).start();
    }

    private void notifyFilteringStarted(final E e) {
        new Thread(() -> listeners.forEach(l -> {
            try {
                l.onFilteringStarted(e);
            } catch (Exception e1) {
                LOGGER.log(Level.WARNING, "Filtering started notification failed. Continuing to next one (if exists)", e1);
            }
        })).start();
    }

    public void addListener(Listener<E> listener) {
        listeners.add(listener);
    }

    public void removeLister(Listener<E> listener) {
        listeners.remove(listener);
    }

    public interface FilterInfo {
        long getInsertTimeMillis();

        int getCount();
    }

    public interface Listener<E> {
        void onFilteringStarted(E e);
        void onFilteringFinished(FilteredItem<E> filteredItem);
    }

    private static class FilterInfoImpl implements FilterInfo {
        private final long insertTimeMillis = System.currentTimeMillis();
        private AtomicInteger count = new AtomicInteger(1);

        int incrementAndGet() {
            return count.incrementAndGet();
        }

        @Override
        public long getInsertTimeMillis() {
            return insertTimeMillis;
        }

        @Override
        public int getCount() {
            return count.get();
        }

        boolean isExpired(long expirationMillis) {
            return insertTimeMillis + expirationMillis < System.currentTimeMillis();
        }
    }

    public static class FilteredItem<E> {
        private final E item;
        private final FilterInfo filterInfo;

        FilteredItem(E item, FilterInfo filterInfo) {
            this.item = item;
            this.filterInfo = filterInfo;
        }

        public E getItem() {
            return item;
        }

        public FilterInfo getFilterInfo() {
            return filterInfo;
        }
    }
}

Source.

【解决方案2】:

DuplicateMessageFilter 可以过滤掉完全重复的消息,可能符合您的第一个要求。目前,过滤器在指定阈值(通过allowedRepititions 设置)后停止所有重复消息,这可能是not be desirable。如果您希望在新消息或based on time 上重置重复计数,则必须扩展过滤器。但是,它没有提供静默日志的摘要。

示例 logback 配置:

<configuration>

  <turboFilter class="ch.qos.logback.classic.turbo.DuplicateMessageFilter" allowedRepetitions="2"/>

  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%date [%thread] %-5level %logger - %msg%n</pattern>
    </encoder>
  </appender>

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

【讨论】:

  • 我刚刚在github 上快速检查了DuplicateMessageFilter 的来源,但找不到有关时间范围的任何信息。因此,在我看来,它只会在达到阈值后丢弃所有消息。 OP 应该提供基于此过滤器的自定义实现。还有a similar filter使用Guava和基于时间的驱逐作为缓存实现。
  • 这很好。实际上,正如实施的那样,一旦达到阈值,所有后续重复项都会被丢弃,这在 OP 的情况下可能是不可取的。过滤器应该按照您的建议进行扩展,尽管我认为这是过滤器中的一个错误。
  • DuplicateMessageFilter 的缺点是它会过滤掉所有重复项。我想更好地控制过滤掉的内容,例如,每个记录器或记录器实例或记录器名称。
【解决方案3】:

我认为我更愿意解决导致日志崩溃的根本问题,而不是消除问题的指示。

您可以为产生滥用消息的类配置一个单独的记录器,并独立于主日志滚动它。

  <!-- Turn on debug logging for our HikariCP connection pool. -->
  <logger name="com.zaxxer.hikari" level="DEBUG" />

  <!-- Turn on debug logging for all loggers under com.stubbornjava -->
  <logger name="com.stubbornjava" level="DEBUG" />

https://www.stubbornjava.com/posts/logging-in-java-with-slf4j-and-logback

【讨论】:

    猜你喜欢
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-08
    • 2010-10-20
    相关资源
    最近更新 更多