Move to using Minecrell's terminal logger directly, for better ANSI control and JLine support.

Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
cpw 2019-02-10 14:35:21 -05:00
parent 13501a8b05
commit c6afb2be3f
No known key found for this signature in database
GPG key ID: 8EB3DF749553B1B7
11 changed files with 38 additions and 982 deletions

View file

@ -181,15 +181,16 @@ project(':forge') {
nativesDirectory: extractNatives.output nativesDirectory: extractNatives.output
] ]
properties = [ properties = [
'org.lwjgl.util.Debug': 'true', // 'org.lwjgl.util.Debug': 'true',
'org.lwjgl.util.DebugLoader': 'true', // 'org.lwjgl.util.DebugLoader': 'true',
'org.lwjgl.system.SharedLibraryExtractDirectory': 'lwjgl_dll', 'org.lwjgl.system.SharedLibraryExtractDirectory': 'lwjgl_dll',
'mc.version': MC_VERSION, 'mc.version': MC_VERSION,
'mcp.version': MCP_VERSION, 'mcp.version': MCP_VERSION,
'forge.version': project.version.substring(MC_VERSION.length() + 1), 'forge.version': project.version.substring(MC_VERSION.length() + 1),
'forge.spec': SPEC_VERSION, 'forge.spec': SPEC_VERSION,
'forge.group': project.group, 'forge.group': project.group,
'fmllauncher.version': SPEC_VERSION 'fmllauncher.version': SPEC_VERSION,
'terminal.ansi': 'true'
] ]
} }
forge_server = { forge_server = {
@ -276,12 +277,13 @@ project(':forge') {
installer 'net.minecraftforge:coremods:0.2.+' installer 'net.minecraftforge:coremods:0.2.+'
installer 'com.electronwill.night-config:core:3.4.2' installer 'com.electronwill.night-config:core:3.4.2'
installer 'com.electronwill.night-config:toml:3.4.2' installer 'com.electronwill.night-config:toml:3.4.2'
installer 'org.jline:jline:3.5.1' installer 'org.jline:jline:3.9.0'
installer 'org.apache.maven:maven-artifact:3.6.0' installer 'org.apache.maven:maven-artifact:3.6.0'
installer 'net.jodah:typetools:0.6.0' installer 'net.jodah:typetools:0.6.0'
installer 'java3d:vecmath:1.5.2' installer 'java3d:vecmath:1.5.2'
installer 'org.apache.logging.log4j:log4j-api:2.11.1' installer 'org.apache.logging.log4j:log4j-api:2.11.1'
installer 'org.apache.logging.log4j:log4j-core:2.11.1' installer 'org.apache.logging.log4j:log4j-core:2.11.1'
installer 'net.minecrell:terminalconsoleappender:1.1.+'
fmllauncherImplementation 'com.google.guava:guava:21.0' fmllauncherImplementation 'com.google.guava:guava:21.0'
fmllauncherImplementation 'com.google.code.gson:gson:2.8.0' fmllauncherImplementation 'com.google.code.gson:gson:2.8.0'
testImplementation "org.junit.jupiter:junit-jupiter-api:5.0.0" testImplementation "org.junit.jupiter:junit-jupiter-api:5.0.0"

View file

@ -67,6 +67,8 @@ public class LaunchTesting
"--assetsDir", assets, "--assetsDir", assets,
"--userProperties", "{}" "--userProperties", "{}"
}, String.class); }, String.class);
} else {
launchArgs = ObjectArrays.concat(launchArgs, args, String.class);
} }
Launcher.main(launchArgs); Launcher.main(launchArgs);
Thread.sleep(10000); Thread.sleep(10000);

View file

@ -19,9 +19,8 @@
package net.minecraftforge.server.console; package net.minecraftforge.server.console;
import net.minecraftforge.server.terminalconsole.TerminalConsoleAppender;
import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.DedicatedServer;
import net.minecrell.terminalconsole.TerminalConsoleAppender;
import org.jline.reader.EndOfFileException; import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader; import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder; import org.jline.reader.LineReaderBuilder;

View file

@ -1,168 +0,0 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.HighlightConverter;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import java.util.List;
/**
* A simplified version of {@link HighlightConverter} that uses
* {@link TerminalConsoleAppender} to detect if Ansi escape codes can be used
* to highlight errors and warnings in the console.
*
* <p>If configured, it will mark all logged errors with a red color and all
* warnings with a yellow color. It can be only used together with
* {@link TerminalConsoleAppender}.</p>
*
* <p>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} may be used
* to force the use of ANSI colors even in unsupported environments.</p>
*
* <p><b>Example usage:</b> {@code %highlightError{%level: %message}}</p>
*/
@Plugin(name = "highlightError", category = PatternConverter.CATEGORY)
@ConverterKeys({ "highlightError" })
@PerformanceSensitive("allocation")
public class HighlightErrorConverter extends LogEventPatternConverter
{
private static final String ANSI_RESET = "\u001B[39;0m";
private static final String ANSI_ERROR = "\u001B[31;1m";
private static final String ANSI_WARN = "\u001B[33;1m";
private final List<PatternFormatter> formatters;
/**
* Construct the converter.
*
* @param formatters The pattern formatters to generate the text to highlight
*/
protected HighlightErrorConverter(List<PatternFormatter> formatters)
{
super("highlightError", null);
this.formatters = formatters;
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo)
{
if (TerminalConsoleAppender.isAnsiSupported())
{
Level level = event.getLevel();
if (level.isMoreSpecificThan(Level.ERROR))
{
format(ANSI_ERROR, event, toAppendTo);
return;
}
else if (level.isMoreSpecificThan(Level.WARN))
{
format(ANSI_WARN, event, toAppendTo);
return;
}
}
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
}
private void format(String style, LogEvent event, StringBuilder toAppendTo)
{
int start = toAppendTo.length();
toAppendTo.append(style);
int end = toAppendTo.length();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
if (toAppendTo.length() == end)
{
// No content so we don't need to append the ANSI escape code
toAppendTo.setLength(start);
}
else
{
// Append reset code after the line
toAppendTo.append(ANSI_RESET);
}
}
@Override
public boolean handlesThrowable()
{
for (final PatternFormatter formatter : formatters)
{
if (formatter.handlesThrowable())
{
return true;
}
}
return false;
}
/**
* Gets a new instance of the {@link HighlightErrorConverter} with the
* specified options.
*
* @param config The current configuration
* @param options The pattern options
* @return The new instance
*/
@Nullable
public static HighlightErrorConverter newInstance(Configuration config, String[] options)
{
if (options.length != 1)
{
LOGGER.error("Incorrect number of options on highlightError. Expected 1 received " + options.length);
return null;
}
if (options[0] == null)
{
LOGGER.error("No pattern supplied on highlightError");
return null;
}
PatternParser parser = PatternLayout.createPatternParser(config);
List<PatternFormatter> formatters = parser.parse(options[0]);
return new HighlightErrorConverter(formatters);
}
}

View file

@ -1,222 +0,0 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.PropertiesUtil;
import java.util.List;
/**
* Replaces Minecraft formatting codes in the result of a pattern with
* appropriate ANSI escape codes. The implementation will only replace valid
* color codes using the section sign (§).
*
* <p>The {@link MinecraftFormattingConverter} can be only used together with
* {@link TerminalConsoleAppender} to detect if the current console supports
* color output. When running in an unsupported environment, it will
* automatically strip all formatting codes instead.</p>
*
* <p>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} may be used
* to force the use of ANSI colors even in unsupported environments. As an
* alternative, {@link #KEEP_FORMATTING_PROPERTY} may be used to keep the
* raw Minecraft formatting codes.</p>
*
* <p><b>Example usage:</b> {@code %minecraftFormatting{%message}}<br>
* It can be configured to always strip formatting codes from the message:
* {@code %minecraftFormatting{%message}{strip}}</p>
*
* @see <a href="http://minecraft.gamepedia.com/Formatting_codes">
* Formatting Codes</a>
*/
@Plugin(name = "minecraftFormatting", category = PatternConverter.CATEGORY)
@ConverterKeys({ "minecraftFormatting" })
@PerformanceSensitive("allocation")
public class MinecraftFormattingConverter extends LogEventPatternConverter
{
/**
* System property that allows disabling the replacement of Minecraft
* formatting codes entirely, keeping them in the console output. For
* some applications they might be easier and more accurate for parsing
* in applications like certain control panels.
*
* <p>If this system property is not set, or set to any value except
* {@code true}, all Minecraft formatting codes will be replaced
* or stripped from the console output.</p>
*/
public static final String KEEP_FORMATTING_PROPERTY = TerminalConsoleAppender.PROPERTY_PREFIX + ".keepMinecraftFormatting";
private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY);
private static final String ANSI_RESET = "\u001B[39;0m";
private static final char COLOR_CHAR = '\u00A7'; // §
private static final String LOOKUP = "0123456789abcdefklmnor";
private static final String[] ansiCodes = new String[] {
"\u001B[0;30;22m", // Black §0
"\u001B[0;34;22m", // Dark Blue §1
"\u001B[0;32;22m", // Dark Green §2
"\u001B[0;36;22m", // Dark Aqua §3
"\u001B[0;31;22m", // Dark Red §4
"\u001B[0;35;22m", // Dark Purple §5
"\u001B[0;33;22m", // Gold §6
"\u001B[0;37;22m", // Gray §7
"\u001B[0;30;1m", // Dark Gray §8
"\u001B[0;34;1m", // Blue §9
"\u001B[0;32;1m", // Green §a
"\u001B[0;36;1m", // Aqua §b
"\u001B[0;31;1m", // Red §c
"\u001B[0;35;1m", // Light Purple §d
"\u001B[0;33;1m", // Yellow §e
"\u001B[0;37;1m", // White §f
"\u001B[5m", // Obfuscated §k
"\u001B[21m", // Bold §l
"\u001B[9m", // Strikethrough §m
"\u001B[4m", // Underline §n
"\u001B[3m", // Italic §o
ANSI_RESET, // Reset §r
};
private final boolean ansi;
private final List<PatternFormatter> formatters;
/**
* Construct the converter.
*
* @param formatters The pattern formatters to generate the text to manipulate
* @param strip If true, the converter will strip all formatting codes
*/
protected MinecraftFormattingConverter(List<PatternFormatter> formatters, boolean strip)
{
super("minecraftFormatting", null);
this.formatters = formatters;
this.ansi = !strip;
}
@Override
public void format(LogEvent event, StringBuilder toAppendTo)
{
int start = toAppendTo.length();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = formatters.size(); i < size; i++)
{
formatters.get(i).format(event, toAppendTo);
}
if (KEEP_FORMATTING || toAppendTo.length() == start)
{
// Skip replacement if disabled or if the content is empty
return;
}
String content = toAppendTo.substring(start);
format(content, toAppendTo, start, ansi && TerminalConsoleAppender.isAnsiSupported());
}
private static void format(String s, StringBuilder result, int start, boolean ansi)
{
int next = s.indexOf(COLOR_CHAR);
int last = s.length() - 1;
if (next == -1 || next == last)
{
return;
}
result.setLength(start + next);
int pos = next;
int format;
do {
if (pos != next)
{
result.append(s, pos, next);
}
format = LOOKUP.indexOf(s.charAt(next + 1));
if (format != -1)
{
if (ansi)
{
result.append(ansiCodes[format]);
}
pos = next += 2;
}
else
{
next++;
}
next = s.indexOf(COLOR_CHAR, next);
} while (next != -1 && next < last);
result.append(s, pos, s.length());
if (ansi)
{
result.append(ANSI_RESET);
}
}
/**
* Gets a new instance of the {@link MinecraftFormattingConverter} with the
* specified options.
*
* @param config The current configuration
* @param options The pattern options
* @return The new instance
*
* @see MinecraftFormattingConverter
*/
@Nullable
public static MinecraftFormattingConverter newInstance(Configuration config, String[] options)
{
if (options.length < 1 || options.length > 2)
{
LOGGER.error("Incorrect number of options on minecraftFormatting. Expected at least 1, max 2 received " + options.length);
return null;
}
if (options[0] == null)
{
LOGGER.error("No pattern supplied on minecraftFormatting");
return null;
}
PatternParser parser = PatternLayout.createPatternParser(config);
List<PatternFormatter> formatters = parser.parse(options[0]);
boolean strip = options.length > 1 && "strip".equals(options[1]);
return new MinecraftFormattingConverter(formatters, strip);
}
}

View file

@ -1,374 +0,0 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.jline.reader.LineReader;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
/**
* An {@link Appender} that uses the JLine 3.x {@link Terminal} to print messages
* to the console.
*
* <p>The JLine {@link Terminal} extends the regular console output with support
* for Ansi escape codes on Windows. Additionally, it's {@link LineReader}
* interface can be used to implement enhanced console input, with an
* persistent input line, as well as command history and command completion.</p>
*
* <p>The {@code TerminalConsole} appender replaces the default {@code Console}
* appender in your log4j configuration. By default, log4j will automatically
* close the standard output when the original {@code Console} appender is
* removed. Consequently, it is necessary to keep an unused {@code Console}
* appender.</p>
*
* <p><b>Example usage:</b></p>
* <pre>{@code <TerminalConsole>
* <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
* </TerminalConsole>
*
* <Console name="SysOut" target="SYSTEM_OUT"/>}</pre>
*
* <p>To use the enhanced console input it is necessary to set the
* {@link LineReader} using {@link #setReader(LineReader)}. The appender will
* then automatically redraw the current prompt. When creating the
* {@link LineReader} it's important to use the {@link Terminal}
* returned by {@link #getTerminal()}. Additionally, the reader should
* be removed from the appender as soon as it's no longer accepting
* input (for example when the user interrupted input using CTRL + C.</p>
*
* <p>By default, the JLine {@link Terminal} is enabled when the application
* is started with an attached terminal session. Usually, this is only the
* case if the application is started from the command line, not if it gets
* started by another application.</p>
*
* <p>In some cases, it might be possible to support a subset of the features
* in these unsupported environments (e.g. only ANSI color codes). In these
* cases, the system properties may be used to override the default behaviour:
* </p>
*
* <ul>
* <li>{@link TerminalConsoleAppender#JLINE_OVERRIDE_PROPERTY} - To enable the extended JLine
* input. By default this will also enable the ANSI escape codes.</li>
* <li>{@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} - To enable the output of ANSI
* escape codes. May be used to force the use of ANSI escape codes
* if JLine is disabled or to disable them if it is enabled.</li>
* </ul>
*/
@Plugin(name = TerminalConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class TerminalConsoleAppender extends AbstractAppender
{
public static final String PLUGIN_NAME = "TerminalConsole";
/**
* The prefix used for all system properties in TerminalConsoleAppender.
*/
public static final String PROPERTY_PREFIX = "terminal";
/**
* System property that allows overriding the default detection of the
* console to force enable or force disable the use of JLine. In some
* environments the automatic detection might not work properly.
* <p>
* <p>If this system property is not set, or set to an invalid value
* (neither {@code true} nor {@code false}) then we will attempt
* to detect the best option automatically.</p>
*/
public static final String JLINE_OVERRIDE_PROPERTY = PROPERTY_PREFIX + ".jline";
/**
* System property that allows overriding the use of ANSI escape codes
* for console formatting even though running in an unsupported
* environment. By default, ANSI color codes are only enabled if JLine
* is enabled. Some systems might be able to handle ANSI escape codes
* but are not capable of JLine's extended input mechanism.
* <p>
* <p>If this system property is not set, or set to an invalid value
* (neither {@code true} nor {@code false}) then we will attempt
* to detect the best option automatically.</p>
*/
public static final String ANSI_OVERRIDE_PROPERTY = PROPERTY_PREFIX + ".ansi";
public static final Boolean ANSI_OVERRIDE = getOptionalBooleanProperty(ANSI_OVERRIDE_PROPERTY);
/**
* We grab the standard output {@link PrintStream} early, otherwise we
* might cause infinite loops later if the application redirects
* {@link System#out} to Log4J.
*/
private static final PrintStream stdout = System.out;
private static boolean initialized;
@Nullable
private static Terminal terminal;
@Nullable
private static LineReader reader;
/**
* Returns the {@link Terminal} that is used to print messages to the
* console. Returns {@code null} in unsupported environments, unless
* overridden using the {@link TerminalConsoleAppender#JLINE_OVERRIDE_PROPERTY} system
* property.
*
* @return The terminal, or null if not supported
* @see TerminalConsoleAppender
*/
@Nullable
public static Terminal getTerminal()
{
return terminal;
}
/**
* Returns the currently configured {@link LineReader} that is used to
* read input from the console. May be null if no {@link LineReader}
* was configured by the environment.
*
* @return The current line reader, or null if none
*/
@Nullable
public static LineReader getReader()
{
return reader;
}
/**
* Sets the {@link LineReader} that is used to read input from the console.
* Setting the {@link LineReader} will allow the appender to automatically
* redraw the input line when a new log message is added.
*
* <p><b>Note:</b> The specified {@link LineReader} must be created with
* the terminal returned by {@link #getTerminal()}.</p>
*
* @param newReader The new line reader
*/
public static void setReader(@Nullable LineReader newReader)
{
if (newReader != null && newReader.getTerminal() != terminal)
{
throw new IllegalArgumentException("Reader was not created with TerminalConsoleAppender.getTerminal()");
}
reader = newReader;
}
/**
* Returns whether ANSI escapes codes should be written to the console
* output.
*
* <p>The return value is {@code true} by default if the JLine terminal
* is enabled and {@code false} otherwise. It may be overridden using
* the {@link TerminalConsoleAppender#ANSI_OVERRIDE_PROPERTY} system property.</p>
*
* @return true if ANSI escapes codes should be written to the console
*/
public static boolean isAnsiSupported()
{
return ANSI_OVERRIDE != null ? ANSI_OVERRIDE : terminal != null;
}
/**
* Constructs a new {@link TerminalConsoleAppender}.
*
* @param name The name of the appender
* @param filter The filter, can be {@code null}
* @param layout The layout to use
* @param ignoreExceptions If {@code true} exceptions encountered when
* appending events are logged, otherwise they are propagated to the
* caller
*/
protected TerminalConsoleAppender(String name, @Nullable Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions)
{
super(name, filter, layout, ignoreExceptions);
initializeTerminal();
}
private static void initializeTerminal()
{
if (!initialized)
{
initialized = true;
// A system property can be used to override our automatic detection
Boolean jlineOverride = getOptionalBooleanProperty(JLINE_OVERRIDE_PROPERTY);
// By default, we disable JLine if there is no terminal attached
// (e.g. if the program output is redirected to a file or if it's
// started by some kind of control panel)
// The same applies to IDEs, they usually provide only a very basic
// console implementation without support for ANSI escape codes
// (used for colors) or characters like \r.
// There are two exceptions:
// 1. IntelliJ IDEA supports colors and control characters
// (We try to detect it using an additional JAR it adds to the classpath)
// 2. The system property forces the use of JLine.
boolean dumb = jlineOverride == Boolean.TRUE || System.getProperty("java.class.path").contains("idea_rt.jar");
if (jlineOverride != Boolean.FALSE)
{
try
{
terminal = TerminalBuilder.builder().dumb(dumb).build();
}
catch (IllegalStateException e)
{
// Unless disabled using one of the exceptions above,
// JLine throws an exception before creating a dumb terminal
// Dumb terminals are used if there is no real terminal attached
// to the application.
if (LOGGER.isDebugEnabled())
{
// Log with stacktrace
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.", e);
}
else
{
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.");
}
}
catch (IOException e)
{
LOGGER.error("Failed to initialize terminal. Falling back to standard output", e);
}
}
}
}
@Nullable
private static Boolean getOptionalBooleanProperty(String name)
{
String value = PropertiesUtil.getProperties().getStringProperty(name);
if (value == null)
{
return null;
}
if (value.equalsIgnoreCase("true"))
{
return Boolean.TRUE;
}
else if (value.equalsIgnoreCase("false"))
{
return Boolean.FALSE;
}
else
{
LOGGER.warn("Invalid value for boolean input property '{}': {}", name, value);
return null;
}
}
@Override
public void append(LogEvent event)
{
if (terminal != null)
{
if (reader != null)
{
// Draw the prompt line again if a reader is available
reader.callWidget(LineReader.CLEAR);
terminal.writer().print(getLayout().toSerializable(event));
reader.callWidget(LineReader.REDRAW_LINE);
reader.callWidget(LineReader.REDISPLAY);
}
else
{
terminal.writer().print(getLayout().toSerializable(event));
}
terminal.writer().flush();
}
else
{
stdout.print(getLayout().toSerializable(event));
}
}
/**
* Closes the JLine {@link Terminal} (if available) and restores the original
* terminal settings.
*
* @throws IOException If an I/O error occurs
*/
public static void close() throws IOException
{
if (initialized)
{
initialized = false;
if (terminal != null)
{
try
{
terminal.close();
}
finally
{
terminal = null;
}
}
}
}
/**
* Creates a new {@link TerminalConsoleAppender}.
*
* @param name The name of the appender
* @param filter The filter, can be {@code null}
* @param layout The layout, can be {@code null}
* @param ignoreExceptions If {@code true} exceptions encountered when
* appending events are logged, otherwise they are propagated to the
* caller
* @return The new appender
*/
@PluginFactory
public static TerminalConsoleAppender createAppender(
@Required(message = "No name provided for TerminalConsoleAppender") @PluginAttribute("name") String name,
@PluginElement("Filter") @Nullable Filter filter,
@PluginElement("Layout") @Nullable Layout<? extends Serializable> layout,
@PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) boolean ignoreExceptions)
{
if (layout == null)
{
layout = PatternLayout.createDefaultLayout();
}
return new TerminalConsoleAppender(name, filter, layout, ignoreExceptions);
}
}

View file

@ -1,7 +0,0 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.server.terminalconsole;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -1,171 +0,0 @@
/*
* TerminalConsoleAppender
* Copyright (c) 2017 Minecrell <https://github.com/Minecrell>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.minecraftforge.server.terminalconsole.util;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.layout.PatternMatch;
import org.apache.logging.log4j.core.layout.PatternSelector;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.util.PerformanceSensitive;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link PatternSelector} that selects patterns based on the logger name.
* Can be used to log messages from different loggers using different patterns.
*
* <p>Multiple logger names may be separated using comma in the
* {@link PatternMatch#getKey() PatternMatch "key"}. The pattern will be applied
* if the logger name matches at least one of them.</p>
*
* <p><b>Example usage:</b></p>
* <pre>{@code <PatternLayout>
* <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss} %level] [%logger]: %msg%n">
* <!-- Log root (empty logger name), "Main", and net.minecrell.* without logger prefix -->
* <PatternMatch key=",Main,net.minecrell." pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
* <PatternMatch key="com.example.Logger" pattern="EXAMPLE: %msg%n"/>
* </LoggerNamePatternSelector>
* </PatternLayout>}</pre>
*/
@Plugin(name = "LoggerNamePatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE)
@PerformanceSensitive("allocation")
public class LoggerNamePatternSelector implements PatternSelector
{
private static class LoggerNameSelector
{
private final String name;
private final boolean isPackage;
private final PatternFormatter[] formatters;
LoggerNameSelector(String name, PatternFormatter[] formatters)
{
this.name = name;
this.isPackage = name.endsWith(".");
this.formatters = formatters;
}
PatternFormatter[] get()
{
return this.formatters;
}
boolean test(String s)
{
return this.isPackage ? s.startsWith(this.name) : s.equals(this.name);
}
}
private final PatternFormatter[] defaultFormatters;
private final List<LoggerNameSelector> formatters = new ArrayList<>();
/**
* Constructs a new {@link LoggerNamePatternSelector}.
*
* @param defaultPattern The default pattern to use if no logger name matches
* @param properties The pattern match rules to use
* @param alwaysWriteExceptions Write exceptions even if pattern does not
* include exception conversion
* @param disableAnsi If true, disable all ANSI escape codes
* @param noConsoleNoAnsi If true and {@link System#console()} is null,
* disable ANSI escape codes
* @param config The configuration
*/
protected LoggerNamePatternSelector(String defaultPattern, PatternMatch[] properties,
boolean alwaysWriteExceptions, boolean disableAnsi, boolean noConsoleNoAnsi, Configuration config)
{
PatternParser parser = PatternLayout.createPatternParser(config);
this.defaultFormatters = toArray(parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi));
for (PatternMatch property : properties)
{
PatternFormatter[] formatters = toArray(parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi));
for (String name : property.getKey().split(","))
{
this.formatters.add(new LoggerNameSelector(name, formatters));
}
}
}
private static PatternFormatter[] toArray(List<PatternFormatter> formatters)
{
return formatters.toArray(new PatternFormatter[formatters.size()]);
}
@Override
public PatternFormatter[] getFormatters(LogEvent event)
{
final String loggerName = event.getLoggerName();
if (loggerName != null)
{
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < this.formatters.size(); i++)
{
LoggerNameSelector selector = this.formatters.get(i);
if (selector.test(loggerName))
{
return selector.get();
}
}
}
return this.defaultFormatters;
}
/**
* Creates a new {@link LoggerNamePatternSelector}.
*
* @param defaultPattern The default pattern to use if no logger name matches
* @param properties The pattern match rules to use
* @param alwaysWriteExceptions Write exceptions even if pattern does not
* include exception conversion
* @param disableAnsi If true, disable all ANSI escape codes
* @param noConsoleNoAnsi If true and {@link System#console()} is null,
* disable ANSI escape codes
* @param config The configuration
* @return The new pattern selector
*/
@PluginFactory
public static LoggerNamePatternSelector createSelector(
@Required(message = "Default pattern is required") @PluginAttribute(value = "defaultPattern") String defaultPattern,
@PluginElement("PatternMatch") PatternMatch[] properties,
@PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) boolean alwaysWriteExceptions,
@PluginAttribute("disableAnsi") boolean disableAnsi,
@PluginAttribute("noConsoleNoAnsi") boolean noConsoleNoAnsi,
@PluginConfiguration Configuration config)
{
return new LoggerNamePatternSelector(defaultPattern, properties, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, config);
}
}

View file

@ -1,7 +0,0 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package net.minecraftforge.server.terminalconsole.util;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -1,42 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" packages="net.minecraftforge.server.terminalconsole.util"> <Configuration status="warn" packages="net.minecrell.terminalconsole,net.minecrell.terminalconsole.util,com.mojang.util">
<Appenders> <Appenders>
<Console name="SysOut" target="SYSTEM_OUT"> <TerminalConsole name="Console">
<PatternLayout> <PatternLayout>
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %msg%n"> <LoggerNamePatternSelector defaultPattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %minecraftFormatting{%msg}%n%xEx}" disableAnsi="${sys:forge.logging.noansi:-true}">
<!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose --> <!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose -->
<PatternMatch key="net.minecraft." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %msg%n"/> <PatternMatch key="net.minecraft." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %msg%n"/> <PatternMatch key="com.mojang." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
</LoggerNamePatternSelector> </LoggerNamePatternSelector>
</PatternLayout> </PatternLayout>
</Console> </TerminalConsole>
<Console name="SysOutColour" target="SYSTEM_OUT">
<PatternLayout>
<LoggerNamePatternSelector defaultPattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %msg%n}">
<!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose -->
<PatternMatch key="net.minecraft." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %msg%n}"/>
<PatternMatch key="com.mojang." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %msg%n}"/>
</LoggerNamePatternSelector>
</PatternLayout>
</Console>
<Queue name="ServerGuiConsole" ignoreExceptions="true"> <Queue name="ServerGuiConsole" ignoreExceptions="true">
<PatternLayout> <PatternLayout>
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %msg%n"> <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %minecraftFormatting{%msg}{strip}">
<!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose --> <!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose -->
<PatternMatch key="net.minecraft." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %msg%n"/> <PatternMatch key="net.minecraft." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}{strip}"/>
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %msg%n"/> <PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}{strip}"/>
</LoggerNamePatternSelector> </LoggerNamePatternSelector>
</PatternLayout> </PatternLayout>
</Queue> </Queue>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz"> <RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{ddMMMyyyy HH:mm:ss.SSS}] [%t/%level] [%logger/%markerSimpleName]: %msg%n"/> <PatternLayout pattern="[%d{ddMMMyyyy HH:mm:ss.SSS}] [%t/%level] [%logger/%markerSimpleName]: %minecraftFormatting{%msg}{strip}%n%xEx"/>
<Policies> <Policies>
<TimeBasedTriggeringPolicy/> <TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/> <OnStartupTriggeringPolicy/>
</Policies> </Policies>
</RollingRandomAccessFile> </RollingRandomAccessFile>
<RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%i.log.gz"> <RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%i.log.gz">
<PatternLayout pattern="[%d{ddMMMyyyy HH:mm:ss.SSS}] [%t/%level] [%logger/%markerSimpleName]: %msg%n"/> <PatternLayout pattern="[%d{ddMMMyyyy HH:mm:ss.SSS}] [%t/%level] [%logger/%markerSimpleName]: %minecraftFormatting{%msg}{strip}%n%xEx"/>
<Policies> <Policies>
<OnStartupTriggeringPolicy/> <OnStartupTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="200MB"/> <SizeBasedTriggeringPolicy size="200MB"/>
@ -64,8 +55,7 @@
<MarkerFilter marker="LOADING" onMatch="${sys:forge.logging.marker.loading:-ACCEPT}" onMismatch="NEUTRAL"/> <MarkerFilter marker="LOADING" onMatch="${sys:forge.logging.marker.loading:-ACCEPT}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="CORE" onMatch="${sys:forge.logging.marker.core:-ACCEPT}" onMismatch="NEUTRAL"/> <MarkerFilter marker="CORE" onMatch="${sys:forge.logging.marker.core:-ACCEPT}" onMismatch="NEUTRAL"/>
</filters> </filters>
<AppenderRef ref="SysOut" level="${sys:forge.logging.console.level:-info}"/> <AppenderRef ref="Console" level="${sys:forge.logging.console.level:-info}"/>
<AppenderRef ref="SysOutColour" level="${sys:forge.logging.colourconsole.level:-off}"/>
<AppenderRef ref="ServerGuiConsole" level="${sys:forge.logging.console.level:-info}"/> <AppenderRef ref="ServerGuiConsole" level="${sys:forge.logging.console.level:-info}"/>
<AppenderRef ref="File" level="${sys:forge.logging.file.level:-info}"/> <AppenderRef ref="File" level="${sys:forge.logging.file.level:-info}"/>
<AppenderRef ref="DebugFile" level="${sys:forge.logging.debugFile.level:-trace}"/> <AppenderRef ref="DebugFile" level="${sys:forge.logging.debugFile.level:-trace}"/>

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" packages="net.minecraftforge.server.terminalconsole,com.mojang.util"> <Configuration status="warn" packages="net.minecrell.terminalconsole,net.minecrell.terminalconsole.util,com.mojang.util">
<Appenders> <Appenders>
<TerminalConsole name="Console"> <TerminalConsole name="Console">
<PatternLayout> <PatternLayout>
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}%n%xEx"> <LoggerNamePatternSelector defaultPattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [%c{2.}/%markerSimpleName]: %minecraftFormatting{%msg}%n%xEx}" disableAnsi="${sys:forge.logging.noansi:-true}">
<!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose --> <!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose -->
<PatternMatch key="net.minecraft." pattern="[%d{HH:mm:ss}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}%n%xEx"/> <PatternMatch key="net.minecraft." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}%n%xEx"/> <PatternMatch key="com.mojang." pattern="%highlight{[%d{HH:mm:ss.SSS}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
</LoggerNamePatternSelector> </LoggerNamePatternSelector>
</PatternLayout> </PatternLayout>
</TerminalConsole> </TerminalConsole>
@ -41,7 +41,19 @@
<Logger level="${sys:forge.logging.mojang.level:-info}" name="net.minecraft"/> <Logger level="${sys:forge.logging.mojang.level:-info}" name="net.minecraft"/>
<Root level="all"> <Root level="all">
<filters> <filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/> <MarkerFilter marker="NETWORK_PACKETS" onMatch="${sys:forge.logging.marker.networking:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="CLASSLOADING" onMatch="${sys:forge.logging.marker.classloading:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="LAUNCHPLUGIN" onMatch="${sys:forge.logging.marker.launchplugin:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="AXFORM" onMatch="${sys:forge.logging.marker.axform:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="EVENTBUS" onMatch="${sys:forge.logging.marker.eventbus:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="DISTXFORM" onMatch="${sys:forge.logging.marker.distxform:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="SCAN" onMatch="${sys:forge.logging.marker.scan:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="REGISTRIES" onMatch="${sys:forge.logging.marker.registries:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="REGISTRYDUMP" onMatch="${sys:forge.logging.marker.registrydump:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="SPLASH" onMatch="${sys:forge.logging.marker.splash:-DENY}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="FORGEMOD" onMatch="${sys:forge.logging.marker.forgemod:-ACCEPT}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="LOADING" onMatch="${sys:forge.logging.marker.loading:-ACCEPT}" onMismatch="NEUTRAL"/>
<MarkerFilter marker="CORE" onMatch="${sys:forge.logging.marker.core:-ACCEPT}" onMismatch="NEUTRAL"/>
</filters> </filters>
<AppenderRef ref="Console" level="${sys:forge.logging.console.level:-info}"/> <AppenderRef ref="Console" level="${sys:forge.logging.console.level:-info}"/>
<AppenderRef ref="ServerGuiConsole" level="${sys:forge.logging.console.level:-info}"/> <AppenderRef ref="ServerGuiConsole" level="${sys:forge.logging.console.level:-info}"/>