diff --git a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java index 92b087d073..76f9186444 100644 --- a/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java +++ b/application/src/main/java/org/togetherjava/tjbot/logging/discord/DiscordLogForwarder.java @@ -35,14 +35,32 @@ final class DiscordLogForwarder { private static final Logger logger = LoggerFactory.getLogger(DiscordLogForwarder.class); private static final int MAX_PENDING_LOGS = 10_000; - private static final int MAX_BATCH_SIZE = WebhookMessage.MAX_EMBEDS; + private static final int MAX_PENDING_LOGS_WARNING_THRESHOLD = 8_000; + private static final ScheduledExecutorService SERVICE = Executors.newSingleThreadScheduledExecutor(); + + private static final int MAX_BATCH_SIZE = WebhookMessage.MAX_EMBEDS; + /** + * The max total length of all descriptions contained in a batch of embeds sent to Discord. + */ + private static final int MAX_BATCH_DESCRIPTION_TOTAL = 6_000; /** - * Has to be small enough for fitting all {@value MAX_BATCH_SIZE} embeds contained in a batch - * into the total character length of ~6000. + * Has to be small enough for fitting most {@value MAX_BATCH_SIZE} embeds contained in a batch + * into the total character length of {@value MAX_BATCH_DESCRIPTION_TOTAL}. + *

+ * This limit is preferred to {@link #MAX_EMBED_DESCRIPTION_SHORT}, as usually only a few + * messages are too large and need to be limited. */ private static final int MAX_EMBED_DESCRIPTION = 1_000; + /** + * Small enough for fitting all {@value MAX_BATCH_SIZE} embeds contained in a batch into the + * total character length of {@value MAX_BATCH_DESCRIPTION_TOTAL}. + *

+ * Used when {@link #MAX_EMBED_DESCRIPTION} lead to exceeding the limit. + */ + private static final int MAX_EMBED_DESCRIPTION_SHORT = 400; + private static final Map LEVEL_TO_AMBIENT_COLOR = Map.of(Level.TRACE, 0x00B362, Level.DEBUG, 0x00A5CE, Level.INFO, 0xAC59FF, Level.WARN, 0xDFDF00, Level.ERROR, 0xBF2200, Level.FATAL, 0xFF8484); @@ -80,6 +98,13 @@ void forwardLogEvent(LogEvent event) { Logs are forwarded to Discord slower than they pile up. Discarding the latest log..."""); return; } + if (pendingLogs.size() >= MAX_PENDING_LOGS_WARNING_THRESHOLD) { + logger.warn(""" + Nearing the max amount of logs that can be buffered. \ + Logs are forwarded to Discord slower than they pile up. \ + Look into the issue, logs will soon be discarded otherwise... + """); + } LogMessage log = LogMessage.ofEvent(event); @@ -95,6 +120,7 @@ private void processPendingLogs() { if (logsToProcess.isEmpty()) { return; } + logsToProcess = validateBatch(logsToProcess); List logBatch = logsToProcess.stream().map(LogMessage::embed).toList(); @@ -112,6 +138,21 @@ private List pollLogsToProcessBatch() { } } + private List validateBatch(List logBatch) { + int totalDescriptionLength = logBatch.stream() + .map(LogMessage::embed) + .map(WebhookEmbed::getDescription) + .mapToInt(description -> description == null ? 0 : description.length()) + .sum(); + + if (totalDescriptionLength >= MAX_BATCH_DESCRIPTION_TOTAL) { + // Shorten logs further to go below limit + return logBatch.stream().map(LogMessage::shortened).toList(); + } + + return new ArrayList<>(logBatch); + } + private record LogMessage(WebhookEmbed embed, Instant timestamp) implements Comparable { @@ -146,6 +187,15 @@ private static String abbreviate(String text, int maxLength) { return text.substring(0, maxLength - 3) + "..."; } + private LogMessage shortened() { + String shortDescription = + abbreviate(embed.getDescription(), MAX_EMBED_DESCRIPTION_SHORT); + WebhookEmbed shortEmbed = + new WebhookEmbedBuilder(embed).setDescription(shortDescription).build(); + + return new LogMessage(shortEmbed, timestamp); + } + @Override public int compareTo(@NotNull LogMessage o) { return timestamp.compareTo(o.timestamp);