This commit is contained in:
Brian Neumann-Fopiano
2026-08-20 10:59:17 -04:00
parent fec655def4
commit 5cacf6d435
62 changed files with 4893 additions and 278 deletions
@@ -19,6 +19,8 @@
package art.arcane.iris.spi;
import java.util.IllegalFormatException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
@@ -33,6 +35,8 @@ import java.util.regex.Pattern;
* Internal to Iris; not a published integration surface.
*/
public final class IrisLogging {
private static final int MAX_ONCE_KEYS = 512;
private static final Set<String> ONCE_KEYS = ConcurrentHashMap.newKeySet();
private static final Pattern LEGACY_COLOR = Pattern.compile("(?i)\\u00a7[0-9A-FK-ORX]");
private static final Pattern MINI_MESSAGE_TAG = Pattern.compile("(?i)</?(?:reset|bold|b|italic|i|underlined?|u|strikethrough|st|obfuscated?|obf|black|dark_blue|dark_green|dark_aqua|dark_red|dark_purple|gold|gray|dark_gray|blue|green|aqua|red|light_purple|yellow|white|gradient|font|hover|click|rainbow)(?::[^>\\n]{0,96})?>|<#[0-9a-f]{6}>");
@@ -54,6 +58,14 @@ public final class IrisLogging {
emit(LogLevel.DEBUG, message);
}
/**
* Logs at {@link LogLevel#NOTICE}, applying {@link #format(String, Object...)} to the arguments. For the
* few lifecycle milestones per boot that have to survive into the server's own log file.
*/
public static void notice(String format, Object... args) {
emit(LogLevel.NOTICE, format(format, args));
}
/**
* Logs at {@link LogLevel#WARN}, applying {@link #format(String, Object...)} to the arguments.
*/
@@ -68,6 +80,25 @@ public final class IrisLogging {
emit(LogLevel.ERROR, format(format, args));
}
/**
* Logs at {@link LogLevel#WARN} the first time {@code key} is seen and at {@link LogLevel#DEBUG} every
* time after, for a condition that is worth stating once but is reached per block, per sample or per
* chunk. Returns true when the warning was the one that got stated, so a caller can attach a stack trace
* or other one-off detail to it.
*
* @see #resetOnceKeys()
*/
public static boolean warnOnce(String key, String format, Object... args) {
return once(LogLevel.WARN, key, format, args);
}
/**
* {@link #warnOnce(String, String, Object...)} at {@link LogLevel#ERROR}.
*/
public static boolean errorOnce(String key, String format, Object... args) {
return once(LogLevel.ERROR, key, format, args);
}
/**
* Emits a player-facing message to the console, preserving colour markup when a platform is bound and
* stripping it via {@link #clean(String)} when one is not.
@@ -156,6 +187,38 @@ public final class IrisLogging {
return MINI_MESSAGE_TAG.matcher(legacyStripped).replaceAll("");
}
/**
* Forgets every key {@link #warnOnce(String, String, Object...)} and {@link #errorOnce} have claimed, so
* the next occurrence is stated again. Called when the platform unbinds: an operator who fixes a pack and
* reloads has to be able to see whether the fix took.
*/
static void resetOnceKeys() {
ONCE_KEYS.clear();
}
private static boolean once(LogLevel level, String key, String format, Object... args) {
String message = format(format, args);
if (!claim(key)) {
emit(LogLevel.DEBUG, message);
return false;
}
emit(level, message);
return true;
}
/**
* The cap is what keeps a key space nobody bounded - a block name, a biome name, a mod id - from becoming
* a leak. Past it nothing new is claimed, which drops later conditions to debug rather than growing.
*/
private static boolean claim(String key) {
if (ONCE_KEYS.size() >= MAX_ONCE_KEYS) {
return false;
}
return ONCE_KEYS.add(key == null ? "null" : key);
}
private static void emit(LogLevel level, String message) {
LogLevel target = level == null ? LogLevel.INFO : level;
if (IrisPlatforms.isBound()) {
@@ -47,9 +47,14 @@ public final class IrisPlatforms {
/**
* Clears the binding. Safe to call when nothing is bound.
* <p>
* Also forgets the keys {@link IrisLogging#warnOnce(String, String, Object...)} has claimed. Unbinding is
* what a reload does, and an operator who has just fixed a pack has to see whether the condition that was
* stated once is still there.
*/
public static synchronized void unbind() {
platform = null;
IrisLogging.resetOnceKeys();
}
/**
@@ -33,6 +33,12 @@ public enum LogLevel {
DEBUG,
/** Normal operational messages. */
INFO,
/**
* Lifecycle milestones an operator reads the server log to find. Not a problem, but adapters route it to
* the host logger rather than to a console sender, so it survives into the log file the server writes.
* Reserved for a handful of events per boot.
*/
NOTICE,
/** Recoverable problems and misconfiguration. */
WARN,
/** Failures; usually paired with {@link IrisPlatform#reportError(Throwable)}. */