From a3d33bee0d82868420028946cc8368157605118e Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 2 Sep 2021 10:57:57 -0400 Subject: [PATCH] logback: add duplicate exception filter This only applies to marked logged messages for overlay rendering and event subscribers, which are common sources of exceptions in 3rd party plugins --- .../runelite/client/eventbus/EventBus.java | 6 +- .../client/ui/overlay/OverlayRenderer.java | 5 +- .../client/util/DeduplicationFilter.java | 96 +++++++++++++++++++ .../src/main/resources/logback.xml | 2 + 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/util/DeduplicationFilter.java diff --git a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java index 7845c80956..ed51aabb6e 100644 --- a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java +++ b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java @@ -44,12 +44,16 @@ import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.extern.slf4j.Slf4j; import net.runelite.client.util.ReflectUtil; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; @Slf4j @RequiredArgsConstructor @ThreadSafe public class EventBus { + private static final Marker DEDUPLICATE = MarkerFactory.getMarker("DEDUPLICATE"); + @Value public static class Subscriber { @@ -82,7 +86,7 @@ public class EventBus */ public EventBus() { - this((e) -> log.warn("Uncaught exception in event subscriber", e)); + this((e) -> log.warn(DEDUPLICATE, "Uncaught exception in event subscriber", e)); } /** diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 463e05725e..99b75c0255 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -67,11 +67,14 @@ import net.runelite.client.input.MouseManager; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.JagexColors; import net.runelite.client.util.ColorUtil; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; @Singleton @Slf4j public class OverlayRenderer extends MouseAdapter implements KeyListener { + private static final Marker DEDUPLICATE = MarkerFactory.getMarker("DEDUPLICATE"); private static final int BORDER = 5; private static final int BORDER_TOP = BORDER + 15; private static final int PADDING = 2; @@ -748,7 +751,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener } catch (Exception ex) { - log.warn("Error during overlay rendering", ex); + log.warn(DEDUPLICATE, "Error during overlay rendering", ex); return; } diff --git a/runelite-client/src/main/java/net/runelite/client/util/DeduplicationFilter.java b/runelite-client/src/main/java/net/runelite/client/util/DeduplicationFilter.java new file mode 100644 index 0000000000..2b99a27e97 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/util/DeduplicationFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.util; + +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 java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +public class DeduplicationFilter extends TurboFilter +{ + private static final Marker deduplicateMarker = MarkerFactory.getMarker("DEDUPLICATE"); + private static final int CACHE_SIZE = 8; + private static final int DUPLICATE_LOG_COUNT = 1000; + + @RequiredArgsConstructor + @EqualsAndHashCode(exclude = {"count"}) + private static class LogException + { + private final String message; + private final StackTraceElement[] stackTraceElements; + private volatile int count; + } + + private final Deque cache = new ConcurrentLinkedDeque<>(); + + @Override + public void stop() + { + cache.clear(); + super.stop(); + } + + @Override + public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) + { + if (marker != deduplicateMarker || logger.isDebugEnabled() || throwable == null) + { + return FilterReply.NEUTRAL; + } + + LogException logException = new LogException(s, throwable.getStackTrace()); + for (LogException e : cache) + { + if (logException.equals(e)) + { + // this iinc is not atomic, but doesn't matter in practice + if (++e.count % DUPLICATE_LOG_COUNT == 0) + { + logger.warn("following log message logged " + DUPLICATE_LOG_COUNT + " times!"); + return FilterReply.NEUTRAL; + } + return FilterReply.DENY; + } + } + + synchronized (cache) + { + if (cache.size() >= CACHE_SIZE) + { + cache.pop(); + } + cache.push(logException); + } + + return FilterReply.NEUTRAL; + } +} diff --git a/runelite-client/src/main/resources/logback.xml b/runelite-client/src/main/resources/logback.xml index d3a5ffbcad..60152930ce 100644 --- a/runelite-client/src/main/resources/logback.xml +++ b/runelite-client/src/main/resources/logback.xml @@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n