Improve server console implementation (#4821)
This commit is contained in:
parent
43895d773e
commit
1db28d072a
18 changed files with 1106 additions and 554 deletions
|
@ -10,7 +10,7 @@
|
||||||
"name": "net.minecraft:launchwrapper:1.12"
|
"name": "net.minecraft:launchwrapper:1.12"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jline:jline:2.13",
|
"name": "org.jline:jline:3.5.1",
|
||||||
"children": ["sources"],
|
"children": ["sources"],
|
||||||
"url" : "http://repo.maven.apache.org/maven2"
|
"url" : "http://repo.maven.apache.org/maven2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,9 +38,14 @@
|
||||||
"clientreq":true
|
"clientreq":true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jline:jline:2.13",
|
"name": "org.jline:jline:3.5.1",
|
||||||
"url" : "http://files.minecraftforge.net/maven/",
|
"url" : "http://files.minecraftforge.net/maven/",
|
||||||
"checksums" : [ "2d9530d0a25daffaffda7c35037b046b627bb171" ],
|
"checksums" : [ "51800e9d7a13608894a5a28eed0f5c7fa2f300fb" ],
|
||||||
|
"serverreq":true,
|
||||||
|
"clientreq":false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "net.java.dev.jna:jna:4.4.0",
|
||||||
"serverreq":true,
|
"serverreq":true,
|
||||||
"clientreq":false
|
"clientreq":false
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class FMLServerTweaker extends FMLTweaker {
|
||||||
|
|
||||||
if (System.getProperty("log4j.configurationFile") == null)
|
if (System.getProperty("log4j.configurationFile") == null)
|
||||||
{
|
{
|
||||||
System.setProperty("log4j.configurationFile", "log4j2.xml");
|
System.setProperty("log4j.configurationFile", "log4j2_server.xml");
|
||||||
((LoggerContext) LogManager.getContext(false)).reconfigure();
|
((LoggerContext) LogManager.getContext(false)).reconfigure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,9 +55,9 @@ public class FMLServerTweaker extends FMLTweaker {
|
||||||
// and deobfuscated parts of the code. Without, the UI won't show anything
|
// and deobfuscated parts of the code. Without, the UI won't show anything
|
||||||
classLoader.addClassLoaderExclusion("com.mojang.util.QueueLogAppender");
|
classLoader.addClassLoaderExclusion("com.mojang.util.QueueLogAppender");
|
||||||
|
|
||||||
classLoader.addClassLoaderExclusion("jline.");
|
classLoader.addClassLoaderExclusion("org.jline.");
|
||||||
classLoader.addClassLoaderExclusion("org.fusesource.");
|
classLoader.addClassLoaderExclusion("com.sun.jna.");
|
||||||
classLoader.addClassLoaderExclusion("net.minecraftforge.server.console.log4j.TerminalConsoleAppender");
|
classLoader.addClassLoaderExclusion("net.minecraftforge.server.terminalconsole.");
|
||||||
|
|
||||||
FMLLaunchHandler.configureForServerLaunch(classLoader, this);
|
FMLLaunchHandler.configureForServerLaunch(classLoader, this);
|
||||||
FMLLaunchHandler.appendCoreMods();
|
FMLLaunchHandler.appendCoreMods();
|
||||||
|
|
|
@ -66,6 +66,8 @@ import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.ObjectArrays;
|
import com.google.common.collect.ObjectArrays;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
|
|
||||||
public class CoreModManager {
|
public class CoreModManager {
|
||||||
private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod");
|
private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod");
|
||||||
|
@ -214,6 +216,16 @@ public class CoreModManager {
|
||||||
{
|
{
|
||||||
FMLLog.log.debug("Enabling runtime deobfuscation");
|
FMLLog.log.debug("Enabling runtime deobfuscation");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (System.getProperty("log4j.configurationFile") == null)
|
||||||
|
{
|
||||||
|
FMLLog.log.info("Detected deobfuscated environment, loading log configs for colored console logs.");
|
||||||
|
// use server logging configs in deobfuscated environment so developers get nicely colored console logs
|
||||||
|
System.setProperty("log4j.configurationFile", "log4j2_server.xml");
|
||||||
|
((LoggerContext) LogManager.getContext(false)).reconfigure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tweaker.injectCascadingTweak("net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker");
|
tweaker.injectCascadingTweak("net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker");
|
||||||
try
|
try
|
||||||
|
|
|
@ -21,6 +21,9 @@ package net.minecraftforge.fml.relauncher;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
|
|
||||||
public class ServerLaunchWrapper {
|
public class ServerLaunchWrapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +41,11 @@ public class ServerLaunchWrapper {
|
||||||
|
|
||||||
private void run(String[] args)
|
private void run(String[] args)
|
||||||
{
|
{
|
||||||
|
if (System.getProperty("log4j.configurationFile") == null)
|
||||||
|
{
|
||||||
|
// Set this early so we don't need to reconfigure later
|
||||||
|
System.setProperty("log4j.configurationFile", "log4j2_server.xml");
|
||||||
|
}
|
||||||
Class<?> launchwrapper = null;
|
Class<?> launchwrapper = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,21 +21,21 @@ package net.minecraftforge.server.console;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import jline.console.completer.Completer;
|
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
public final class ConsoleCommandCompleter implements Completer
|
final class ConsoleCommandCompleter implements Completer
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private final DedicatedServer server;
|
private final DedicatedServer server;
|
||||||
|
|
||||||
|
@ -45,10 +45,9 @@ public final class ConsoleCommandCompleter implements Completer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int complete(String buffer, int cursor, List<CharSequence> candidates)
|
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates)
|
||||||
{
|
{
|
||||||
int len = buffer.length();
|
String buffer = line.line();
|
||||||
|
|
||||||
boolean prefix;
|
boolean prefix;
|
||||||
if (buffer.isEmpty() || buffer.charAt(0) != '/')
|
if (buffer.isEmpty() || buffer.charAt(0) != '/')
|
||||||
{
|
{
|
||||||
|
@ -61,44 +60,19 @@ public final class ConsoleCommandCompleter implements Completer
|
||||||
}
|
}
|
||||||
|
|
||||||
final String input = buffer;
|
final String input = buffer;
|
||||||
Future<List<String>> tabComplete = this.server.callFromMainThread(new Callable<List<String>>() {
|
Future<List<String>> tabComplete = this.server.callFromMainThread(() -> this.server.getTabCompletions(this.server, input, this.server.getPosition(), false));
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> call() throws Exception
|
|
||||||
{
|
|
||||||
return ConsoleCommandCompleter.this.server.getTabCompletions(ConsoleCommandCompleter.this.server, input,
|
|
||||||
ConsoleCommandCompleter.this.server.getPosition(), false/* we're not a command block */);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<String> completions = tabComplete.get();
|
for (String completion : tabComplete.get())
|
||||||
Collections.sort(completions);
|
|
||||||
if (prefix)
|
|
||||||
{
|
{
|
||||||
candidates.addAll(completions);
|
if (!completion.isEmpty())
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (String completion : completions)
|
|
||||||
{
|
{
|
||||||
candidates.add(completion.charAt(0) == '/' ? completion.substring(1) : completion);
|
boolean hasPrefix = prefix || completion.charAt(0) != '/';
|
||||||
|
Candidate candidate = new Candidate(hasPrefix ? completion : completion.substring(1));
|
||||||
|
candidates.add(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int pos = buffer.lastIndexOf(' ');
|
|
||||||
if (pos == -1)
|
|
||||||
{
|
|
||||||
return cursor - len;
|
|
||||||
}
|
|
||||||
else if (prefix)
|
|
||||||
{
|
|
||||||
return cursor - len + pos + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return cursor - len + pos;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException e)
|
||||||
{
|
{
|
||||||
|
@ -108,8 +82,6 @@ public final class ConsoleCommandCompleter implements Completer
|
||||||
{
|
{
|
||||||
logger.error("Failed to tab complete", e);
|
logger.error("Failed to tab complete", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* Minecraft Forge
|
|
||||||
* Copyright (c) 2016.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation version 2.1
|
|
||||||
* of the License.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.server.console;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.fusesource.jansi.Ansi;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
|
|
||||||
import net.minecraft.util.text.TextFormatting;
|
|
||||||
|
|
||||||
public final class ConsoleFormatter implements Function<String, String>
|
|
||||||
{
|
|
||||||
|
|
||||||
public ConsoleFormatter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String RESET = Ansi.ansi().reset().toString();
|
|
||||||
|
|
||||||
private static final ImmutableMap<Pattern, String> REPLACEMENTS = ImmutableMap.<Pattern, String> builder()
|
|
||||||
.put(compile(TextFormatting.BLACK), Ansi.ansi().reset().fg(Ansi.Color.BLACK).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_BLUE), Ansi.ansi().reset().fg(Ansi.Color.BLUE).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_GREEN), Ansi.ansi().reset().fg(Ansi.Color.GREEN).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_AQUA), Ansi.ansi().reset().fg(Ansi.Color.CYAN).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_RED), Ansi.ansi().reset().fg(Ansi.Color.RED).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_PURPLE), Ansi.ansi().reset().fg(Ansi.Color.MAGENTA).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.GOLD), Ansi.ansi().reset().fg(Ansi.Color.YELLOW).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.GRAY), Ansi.ansi().reset().fg(Ansi.Color.WHITE).boldOff().toString())
|
|
||||||
.put(compile(TextFormatting.DARK_GRAY), Ansi.ansi().reset().fg(Ansi.Color.BLACK).bold().toString())
|
|
||||||
.put(compile(TextFormatting.BLUE), Ansi.ansi().reset().fg(Ansi.Color.BLUE).bold().toString())
|
|
||||||
.put(compile(TextFormatting.GREEN), Ansi.ansi().reset().fg(Ansi.Color.GREEN).bold().toString())
|
|
||||||
.put(compile(TextFormatting.AQUA), Ansi.ansi().reset().fg(Ansi.Color.CYAN).bold().toString())
|
|
||||||
.put(compile(TextFormatting.RED), Ansi.ansi().reset().fg(Ansi.Color.RED).bold().toString())
|
|
||||||
.put(compile(TextFormatting.LIGHT_PURPLE), Ansi.ansi().reset().fg(Ansi.Color.MAGENTA).bold().toString())
|
|
||||||
.put(compile(TextFormatting.YELLOW), Ansi.ansi().reset().fg(Ansi.Color.YELLOW).bold().toString())
|
|
||||||
.put(compile(TextFormatting.WHITE), Ansi.ansi().reset().fg(Ansi.Color.WHITE).bold().toString())
|
|
||||||
.put(compile(TextFormatting.OBFUSCATED), Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString())
|
|
||||||
.put(compile(TextFormatting.BOLD), Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString())
|
|
||||||
.put(compile(TextFormatting.STRIKETHROUGH), Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString())
|
|
||||||
.put(compile(TextFormatting.UNDERLINE), Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString())
|
|
||||||
.put(compile(TextFormatting.ITALIC), Ansi.ansi().a(Ansi.Attribute.ITALIC).toString())
|
|
||||||
.put(compile(TextFormatting.RESET), RESET)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static Pattern compile(TextFormatting formatting)
|
|
||||||
{
|
|
||||||
return Pattern.compile(formatting.toString(), Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String apply(String text)
|
|
||||||
{
|
|
||||||
for (Map.Entry<Pattern, String> entry : REPLACEMENTS.entrySet())
|
|
||||||
{
|
|
||||||
text = entry.getKey().matcher(text).replaceAll(entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return text + RESET;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,197 +0,0 @@
|
||||||
/*
|
|
||||||
* Minecraft Forge
|
|
||||||
* Copyright (c) 2016.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation version 2.1
|
|
||||||
* of the License.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.server.console;
|
|
||||||
|
|
||||||
import static jline.TerminalFactory.OFF;
|
|
||||||
import static jline.console.ConsoleReader.RESET_LINE;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
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.layout.PatternLayout;
|
|
||||||
import org.apache.logging.log4j.core.util.Booleans;
|
|
||||||
import org.apache.logging.log4j.util.PropertiesUtil;
|
|
||||||
import org.fusesource.jansi.AnsiConsole;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import com.google.common.base.Functions;
|
|
||||||
|
|
||||||
import jline.TerminalFactory;
|
|
||||||
import jline.console.ConsoleReader;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO 1.13 remove this class
|
|
||||||
* @deprecated use the version in the log4j sub-package: {@link net.minecraftforge.server.console.log4j.TerminalConsoleAppender}
|
|
||||||
* This is being moved into its own package so that the log configuration doesn't load this whole package,
|
|
||||||
* which has references to Minecraft in it that can load lots of extra classes.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@Plugin(name = "TerminalConsole", category = "Core", elementType = "appender", printObject = true)
|
|
||||||
public class TerminalConsoleAppender extends AbstractAppender
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final boolean ENABLE_JLINE = PropertiesUtil.getProperties().getBooleanProperty("jline.enable", true);
|
|
||||||
|
|
||||||
private static final PrintStream out = System.out;
|
|
||||||
|
|
||||||
private static boolean initialized;
|
|
||||||
private static ConsoleReader reader;
|
|
||||||
|
|
||||||
public static ConsoleReader getReader()
|
|
||||||
{
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Function<String, String> formatter = Functions.identity();
|
|
||||||
|
|
||||||
public static void setFormatter(Function<String, String> format)
|
|
||||||
{
|
|
||||||
formatter = format != null ? format : Functions.identity();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TerminalConsoleAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions)
|
|
||||||
{
|
|
||||||
super(name, filter, layout, ignoreExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PluginFactory
|
|
||||||
@Nullable
|
|
||||||
public static TerminalConsoleAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filters") Filter filter,
|
|
||||||
@PluginElement("Layout") Layout<? extends Serializable> layout, @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) String ignore)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
LOGGER.error("No name provided for TerminalConsoleAppender");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (layout == null)
|
|
||||||
{
|
|
||||||
layout = PatternLayout.newBuilder().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
|
|
||||||
|
|
||||||
// This is handled by jline
|
|
||||||
System.setProperty("log4j.skipJansi", "true");
|
|
||||||
return new TerminalConsoleAppender(name, filter, layout, ignoreExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
// Initialize the reader if that hasn't happened yet
|
|
||||||
if (!initialized && reader == null)
|
|
||||||
{
|
|
||||||
initialized = true;
|
|
||||||
|
|
||||||
if (ENABLE_JLINE)
|
|
||||||
{
|
|
||||||
final boolean hasConsole = System.console() != null;
|
|
||||||
if (hasConsole)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AnsiConsole.systemInstall();
|
|
||||||
reader = new ConsoleReader();
|
|
||||||
reader.setExpandEvents(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Failed to initialize terminal. Falling back to default.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader == null)
|
|
||||||
{
|
|
||||||
// Eclipse doesn't support colors and characters like \r so enabling jline2 on it will
|
|
||||||
// just cause a lot of issues with empty lines and weird characters.
|
|
||||||
// Enable jline2 only on IntelliJ IDEA to prevent that.
|
|
||||||
// Also see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=76936
|
|
||||||
|
|
||||||
if (hasConsole || System.getProperty("java.class.path").contains("idea_rt.jar"))
|
|
||||||
{
|
|
||||||
// Disable advanced jline features
|
|
||||||
TerminalFactory.configure(OFF);
|
|
||||||
TerminalFactory.reset();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
reader = new ConsoleReader();
|
|
||||||
reader.setExpandEvents(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Failed to initialize fallback terminal. Falling back to standard output console.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(LogEvent event)
|
|
||||||
{
|
|
||||||
if (reader != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Writer out = reader.getOutput();
|
|
||||||
out.write(RESET_LINE);
|
|
||||||
out.write(formatEvent(event));
|
|
||||||
|
|
||||||
reader.drawLine();
|
|
||||||
reader.flush();
|
|
||||||
}
|
|
||||||
catch (IOException ignored)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
out.print(formatEvent(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String formatEvent(LogEvent event)
|
|
||||||
{
|
|
||||||
return formatter.apply(getLayout().toSerializable(event).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -19,63 +19,73 @@
|
||||||
|
|
||||||
package net.minecraftforge.server.console;
|
package net.minecraftforge.server.console;
|
||||||
|
|
||||||
import java.io.IOException;
|
import net.minecraftforge.server.terminalconsole.TerminalConsoleAppender;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import jline.console.ConsoleReader;
|
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.util.text.TextFormatting;
|
import org.jline.reader.EndOfFileException;
|
||||||
import net.minecraftforge.server.console.log4j.TerminalConsoleAppender;
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReaderBuilder;
|
||||||
|
import org.jline.reader.UserInterruptException;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
|
||||||
public final class TerminalHandler
|
public final class TerminalHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
|
||||||
|
|
||||||
private TerminalHandler()
|
private TerminalHandler()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean handleCommands(DedicatedServer server)
|
public static boolean handleCommands(DedicatedServer server)
|
||||||
{
|
{
|
||||||
final ConsoleReader reader = TerminalConsoleAppender.getReader();
|
final Terminal terminal = TerminalConsoleAppender.getTerminal();
|
||||||
if (reader != null)
|
if (terminal == null)
|
||||||
{
|
return false;
|
||||||
TerminalConsoleAppender.setFormatter(new ConsoleFormatter());
|
|
||||||
reader.addCompleter(new ConsoleCommandCompleter(server));
|
|
||||||
|
|
||||||
|
LineReader reader = LineReaderBuilder.builder()
|
||||||
|
.appName("Forge")
|
||||||
|
.terminal(terminal)
|
||||||
|
.completer(new ConsoleCommandCompleter(server))
|
||||||
|
.build();
|
||||||
|
reader.setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION);
|
||||||
|
reader.unsetOpt(LineReader.Option.INSERT_TAB);
|
||||||
|
|
||||||
|
TerminalConsoleAppender.setReader(reader);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
String line;
|
String line;
|
||||||
while (!server.isServerStopped() && server.isServerRunning())
|
while (!server.isServerStopped() && server.isServerRunning())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
line = reader.readLine("> ");
|
line = reader.readLine("> ");
|
||||||
if (line == null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.trim();
|
|
||||||
if (!line.isEmpty())
|
|
||||||
{
|
|
||||||
server.addPendingCommand(line, server);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (EndOfFileException ignored)
|
||||||
{
|
{
|
||||||
logger.error("Exception handling console input", e);
|
// Continue reading after EOT
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
line = line.trim();
|
||||||
|
if (!line.isEmpty())
|
||||||
|
{
|
||||||
|
server.addPendingCommand(line, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
catch (UserInterruptException e)
|
||||||
{
|
{
|
||||||
TerminalConsoleAppender.setFormatter(TextFormatting::getTextWithoutFormattingCodes);
|
server.initiateShutdown();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TerminalConsoleAppender.setReader(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
/*
|
|
||||||
* Minecraft Forge
|
|
||||||
* Copyright (c) 2016.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation version 2.1
|
|
||||||
* of the License.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.server.console.log4j;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static jline.TerminalFactory.OFF;
|
|
||||||
import static jline.console.ConsoleReader.RESET_LINE;
|
|
||||||
|
|
||||||
import com.google.common.base.Functions;
|
|
||||||
import jline.TerminalFactory;
|
|
||||||
import jline.console.ConsoleReader;
|
|
||||||
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.layout.PatternLayout;
|
|
||||||
import org.apache.logging.log4j.core.util.Booleans;
|
|
||||||
import org.apache.logging.log4j.util.PropertiesUtil;
|
|
||||||
import org.fusesource.jansi.AnsiConsole;
|
|
||||||
|
|
||||||
@Plugin(name = "TerminalConsole", category = "Core", elementType = "appender", printObject = true)
|
|
||||||
public class TerminalConsoleAppender extends AbstractAppender
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final boolean ENABLE_JLINE = PropertiesUtil.getProperties().getBooleanProperty("jline.enable", true);
|
|
||||||
|
|
||||||
private static final PrintStream out = System.out;
|
|
||||||
|
|
||||||
private static boolean initialized;
|
|
||||||
private static ConsoleReader reader;
|
|
||||||
|
|
||||||
public static ConsoleReader getReader()
|
|
||||||
{
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Function<String, String> formatter = Functions.identity();
|
|
||||||
|
|
||||||
public static void setFormatter(Function<String, String> format)
|
|
||||||
{
|
|
||||||
formatter = format != null ? format : Functions.identity();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TerminalConsoleAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions)
|
|
||||||
{
|
|
||||||
super(name, filter, layout, ignoreExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PluginFactory
|
|
||||||
@Nullable
|
|
||||||
public static TerminalConsoleAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filters") Filter filter,
|
|
||||||
@PluginElement("Layout") Layout<? extends Serializable> layout, @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) String ignore)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
LOGGER.error("No name provided for TerminalConsoleAppender");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (layout == null)
|
|
||||||
{
|
|
||||||
layout = PatternLayout.newBuilder().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
|
|
||||||
|
|
||||||
// This is handled by jline
|
|
||||||
System.setProperty("log4j.skipJansi", "true");
|
|
||||||
return new TerminalConsoleAppender(name, filter, layout, ignoreExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
// Initialize the reader if that hasn't happened yet
|
|
||||||
if (!initialized && reader == null)
|
|
||||||
{
|
|
||||||
initialized = true;
|
|
||||||
|
|
||||||
if (ENABLE_JLINE)
|
|
||||||
{
|
|
||||||
final boolean hasConsole = System.console() != null;
|
|
||||||
if (hasConsole)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AnsiConsole.systemInstall();
|
|
||||||
reader = new ConsoleReader();
|
|
||||||
reader.setExpandEvents(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Failed to initialize terminal. Falling back to default.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader == null)
|
|
||||||
{
|
|
||||||
// Eclipse doesn't support colors and characters like \r so enabling jline2 on it will
|
|
||||||
// just cause a lot of issues with empty lines and weird characters.
|
|
||||||
// Enable jline2 only on IntelliJ IDEA to prevent that.
|
|
||||||
// Also see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=76936
|
|
||||||
|
|
||||||
if (hasConsole || System.getProperty("java.class.path").contains("idea_rt.jar"))
|
|
||||||
{
|
|
||||||
// Disable advanced jline features
|
|
||||||
TerminalFactory.configure(OFF);
|
|
||||||
TerminalFactory.reset();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
reader = new ConsoleReader();
|
|
||||||
reader.setExpandEvents(false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Failed to initialize fallback terminal. Falling back to standard output console.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(LogEvent event)
|
|
||||||
{
|
|
||||||
if (reader != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Writer out = reader.getOutput();
|
|
||||||
out.write(RESET_LINE);
|
|
||||||
out.write(formatEvent(event));
|
|
||||||
|
|
||||||
reader.drawLine();
|
|
||||||
reader.flush();
|
|
||||||
}
|
|
||||||
catch (IOException ignored)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
out.print(formatEvent(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String formatEvent(LogEvent event)
|
|
||||||
{
|
|
||||||
return formatter.apply(getLayout().toSerializable(event).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,374 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
package net.minecraftforge.server.terminalconsole;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
@MethodsReturnNonnullByDefault
|
||||||
|
package net.minecraftforge.server.terminalconsole.util;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -1,13 +1,23 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Configuration status="warn" packages="com.mojang.util,net.minecraftforge.server.console.log4j">
|
<Configuration status="warn" packages="net.minecraftforge.server.terminalconsole.util">
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<TerminalConsole name="Console">
|
<Console name="SysOut" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
|
<PatternLayout>
|
||||||
</TerminalConsole>
|
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n">
|
||||||
<!-- Keep a console appender open so log4j2 doesn't close our main out stream if we redirect System.out to the logger -->
|
<!-- don't include the full logger name for Mojang's logs since they use full class names and it's very verbose -->
|
||||||
<Console name="SysOut" target="SYSTEM_OUT"/>
|
<PatternMatch key="net.minecraft." pattern="[%d{HH:mm:ss}] [%t/%level] [minecraft/%logger{1}]: %msg%n"/>
|
||||||
|
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss}] [%t/%level] [mojang/%logger{1}]: %msg%n"/>
|
||||||
|
</LoggerNamePatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
</Console>
|
||||||
<Queue name="ServerGuiConsole" ignoreExceptions="true">
|
<Queue name="ServerGuiConsole" ignoreExceptions="true">
|
||||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n" />
|
<PatternLayout>
|
||||||
|
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %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="[%d{HH:mm:ss}] [%t/%level] [minecraft/%logger{1}]: %msg%n"/>
|
||||||
|
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss}] [%t/%level] [mojang/%logger{1}]: %msg%n"/>
|
||||||
|
</LoggerNamePatternSelector>
|
||||||
|
</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{HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
|
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
|
||||||
|
@ -33,9 +43,9 @@
|
||||||
<filters>
|
<filters>
|
||||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
|
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||||
</filters>
|
</filters>
|
||||||
<AppenderRef ref="Console" level="info"/>
|
<AppenderRef ref="SysOut" level="info"/>
|
||||||
<AppenderRef ref="File" level="info"/>
|
|
||||||
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
||||||
|
<AppenderRef ref="File" level="info"/>
|
||||||
<AppenderRef ref="DebugFile"/>
|
<AppenderRef ref="DebugFile"/>
|
||||||
</Root>
|
</Root>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
|
|
52
src/main/resources/log4j2_server.xml
Normal file
52
src/main/resources/log4j2_server.xml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="warn" packages="net.minecraftforge.server.terminalconsole">
|
||||||
|
<Appenders>
|
||||||
|
<TerminalConsole name="Console">
|
||||||
|
<PatternLayout>
|
||||||
|
<LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}%n%xEx}">
|
||||||
|
<!-- 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="%highlightError{[%d{HH:mm:ss}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
|
||||||
|
<PatternMatch key="com.mojang." pattern="%highlightError{[%d{HH:mm:ss}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}%n%xEx}"/>
|
||||||
|
</LoggerNamePatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
</TerminalConsole>
|
||||||
|
<Queue name="ServerGuiConsole" ignoreExceptions="true">
|
||||||
|
<PatternLayout>
|
||||||
|
<LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}{strip}%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="[%d{HH:mm:ss}] [%t/%level] [minecraft/%logger{1}]: %minecraftFormatting{%msg}{strip}%n"/>
|
||||||
|
<PatternMatch key="com.mojang." pattern="[%d{HH:mm:ss}] [%t/%level] [mojang/%logger{1}]: %minecraftFormatting{%msg}{strip}%n"/>
|
||||||
|
</LoggerNamePatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
</Queue>
|
||||||
|
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}{strip}%n"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy/>
|
||||||
|
<OnStartupTriggeringPolicy/>
|
||||||
|
</Policies>
|
||||||
|
</RollingRandomAccessFile>
|
||||||
|
<RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%i.log.gz">
|
||||||
|
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %minecraftFormatting{%msg}{strip}%n"/>
|
||||||
|
<Policies>
|
||||||
|
<OnStartupTriggeringPolicy/>
|
||||||
|
<SizeBasedTriggeringPolicy size="200MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="5" fileIndex="min"/>
|
||||||
|
</RollingRandomAccessFile>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<!-- make sure mojang's logging is set to 'info' so that their LOGGER.isDebugEnabled() behavior isn't active -->
|
||||||
|
<Logger level="info" name="com.mojang"/>
|
||||||
|
<Logger level="info" name="net.minecraft"/>
|
||||||
|
<Root level="all">
|
||||||
|
<filters>
|
||||||
|
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||||
|
</filters>
|
||||||
|
<AppenderRef ref="Console" level="info"/>
|
||||||
|
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
||||||
|
<AppenderRef ref="File" level="info"/>
|
||||||
|
<AppenderRef ref="DebugFile"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
Loading…
Reference in a new issue