Class ConsoleMirror

java.lang.Object
com.ultikits.ultitools.handler.ConsoleMirror

@Internal public final class ConsoleMirror extends Object
Makes the panel's log stream a mirror of the server console (as of 6.3.0).

Why it exists. The stream's handler, SystemLogHandler, sits on the java.util.logging root logger, which receives every plugin's lines. Paper prints its own output through Log4j instead: command feedback (MinecraftServer#sendSystemMessage), a module's reply to the console sender (TerminalConsoleCommandSender, a logger named after Log4j's root logger), joins, chat, vanilla warnings and player command lines. Log4j never hands those to java.util.logging, so before this class none of them reached the panel -- in particular not the output of a command the panel itself sent.

How. An appender on Log4j's root logger converts each event into a LogRecord and hands it to the stream handler currently attached to the java.util.logging root logger: EarlyLogCapture before the panel connection opens, SystemLogHandler after. Every filter of that path therefore applies unchanged -- excluded loggers, enabled levels, batching, the start-up replay, the same-thread re-entry guard, error reporting. ANSI colour sequences are removed, as Paper's own latest.log does.

No duplicates. Paper also forwards every java.util.logging record into Log4j, through org.bukkit.craftbukkit.util.ForwardLogHandler on the root logger, so a plugin line would otherwise arrive twice. The java.util.logging handler stays the only route for those lines -- it is the one that sees the panel-connection mark (PanelConnectionLog) and the levels below INFO -- and the appender skips what Paper's forwarder produces: while installed, each forwarder is wrapped in a handler that marks the current thread for the duration of its own publish, and Log4j calls the appender on that same thread. Uninstalling puts the original forwarder back.

Asynchronous loggers. If the server's Log4j configuration uses asynchronous loggers, the appender runs on another thread, where neither the forwarder mark nor the re-entry guard holds. The mirror is then not installed and a warning says so; the stream carries the plugin lines only, as before 6.3.0.

No forwarder, no mirror (fail closed, UltiTools-Reborn#583 F1). The appender can tell a forwarded plugin line from a console line only through the forwarder it wrapped. When no root handler has the binary name org.bukkit.craftbukkit.util.ForwardLogHandler -- a fork, a build that relocates CraftBukkit, something that replaced the forwarder -- nothing is wrapped, and an appender would deliver every plugin line a second time (and stream panel-connection lines the panel must never get back). The mirror is then not installed: install() returns false, nothing is added to Log4j, and one WARNING per server run says the panel will not mirror the console; the stream carries the plugin lines only, as before 6.3.0.

Since:
6.3.0
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    private static final class 
    Marks the current thread while Paper's forwarder copies a record into Log4j.
    private static final class 
    The appender on Log4j's root logger.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    private static final Pattern
    An ANSI CSI sequence, such as a colour code.
    The installed appender, or null.
    static final String
    The name of the appender on Log4j's root logger.
    private static org.apache.logging.log4j.core.LoggerContext
    The Log4j context the appender is installed in.
    (package private) static final String
    The binary name of Paper's java.util.logging to Log4j forwarder.
    private static boolean
    Whether the missing-forwarder WARNING was logged in this server run (#583 F1): install() runs again on every panel reconnect, and the outcome cannot change while the server runs.
    private static final ThreadLocal<int[]>
    How deep the current thread is inside a wrapped forwarder's publish.
    private static final Object
     
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    private
     
  • Method Summary

    Modifier and Type
    Method
    Description
    (package private) static void
    Hands a converted Log4j event to the stream handler attached to the java.util.logging root logger, if any.
    static boolean
    Installs the mirror; does nothing when it is already installed.
    private static boolean
     
    static boolean
    Whether the mirror is installed.
    private static Level
    toJulLevel(org.apache.logging.log4j.Level level)
     
    (package private) static LogRecord
    toRecord(org.apache.logging.log4j.core.LogEvent event)
    Converts a Log4j event into the record the stream handler expects.
    static void
    Removes the mirror and puts Paper's forwarder back; does nothing when it is not installed.
    private static void
     
    private static boolean
    usesAsyncLoggers(org.apache.logging.log4j.core.LoggerContext ctx, org.apache.logging.log4j.core.config.Configuration configuration)
     
    private static void
    warn(String message)
     
    private static int
    Wraps every Paper forwarder on the root logger.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • APPENDER_NAME

      public static final String APPENDER_NAME
      The name of the appender on Log4j's root logger.
      See Also:
    • FORWARD_LOG_HANDLER

      static final String FORWARD_LOG_HANDLER
      The binary name of Paper's java.util.logging to Log4j forwarder.
      See Also:
    • ANSI

      private static final Pattern ANSI
      An ANSI CSI sequence, such as a colour code.
    • FORWARDING

      private static final ThreadLocal<int[]> FORWARDING
      How deep the current thread is inside a wrapped forwarder's publish.
    • LOCK

      private static final Object LOCK
    • appender

      private static ConsoleMirror.MirrorAppender appender
      The installed appender, or null. Guarded by LOCK.
    • context

      private static org.apache.logging.log4j.core.LoggerContext context
      The Log4j context the appender is installed in. Guarded by LOCK.
    • forwarderMissingWarned

      private static boolean forwarderMissingWarned
      Whether the missing-forwarder WARNING was logged in this server run (#583 F1): install() runs again on every panel reconnect, and the outcome cannot change while the server runs. Guarded by LOCK.
  • Constructor Details

    • ConsoleMirror

      private ConsoleMirror()
  • Method Details

    • install

      public static boolean install()
      Installs the mirror; does nothing when it is already installed. Not installed -- and false returned -- when the server's Log4j uses asynchronous loggers, or when Paper's forwarder is not on the root logger (fail closed, see the class description).
      Returns:
      whether the mirror is installed afterwards
    • installLocked

      private static boolean installLocked()
    • uninstall

      public static void uninstall()
      Removes the mirror and puts Paper's forwarder back; does nothing when it is not installed.
    • isInstalled

      public static boolean isInstalled()
      Whether the mirror is installed.
      Returns:
      whether the mirror is installed
    • usesAsyncLoggers

      private static boolean usesAsyncLoggers(org.apache.logging.log4j.core.LoggerContext ctx, org.apache.logging.log4j.core.config.Configuration configuration)
    • wrapForwarders

      private static int wrapForwarders()
      Wraps every Paper forwarder on the root logger.
      Returns:
      how many were wrapped; 0 means none was found and nothing was changed
    • unwrapForwarders

      private static void unwrapForwarders()
    • deliver

      static void deliver(LogRecord record)
      Hands a converted Log4j event to the stream handler attached to the java.util.logging root logger, if any.
    • toRecord

      static LogRecord toRecord(org.apache.logging.log4j.core.LogEvent event)
      Converts a Log4j event into the record the stream handler expects.
    • toJulLevel

      private static Level toJulLevel(org.apache.logging.log4j.Level level)
    • warn

      private static void warn(String message)