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": "jline:jline:2.13",
|
||||
"name": "org.jline:jline:3.5.1",
|
||||
"children": ["sources"],
|
||||
"url" : "http://repo.maven.apache.org/maven2"
|
||||
},
|
||||
|
|
|
@ -38,9 +38,14 @@
|
|||
"clientreq":true
|
||||
},
|
||||
{
|
||||
"name": "jline:jline:2.13",
|
||||
"name": "org.jline:jline:3.5.1",
|
||||
"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,
|
||||
"clientreq":false
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@ public class FMLServerTweaker extends FMLTweaker {
|
|||
|
||||
if (System.getProperty("log4j.configurationFile") == null)
|
||||
{
|
||||
System.setProperty("log4j.configurationFile", "log4j2.xml");
|
||||
System.setProperty("log4j.configurationFile", "log4j2_server.xml");
|
||||
((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
|
||||
classLoader.addClassLoaderExclusion("com.mojang.util.QueueLogAppender");
|
||||
|
||||
classLoader.addClassLoaderExclusion("jline.");
|
||||
classLoader.addClassLoaderExclusion("org.fusesource.");
|
||||
classLoader.addClassLoaderExclusion("net.minecraftforge.server.console.log4j.TerminalConsoleAppender");
|
||||
classLoader.addClassLoaderExclusion("org.jline.");
|
||||
classLoader.addClassLoaderExclusion("com.sun.jna.");
|
||||
classLoader.addClassLoaderExclusion("net.minecraftforge.server.terminalconsole.");
|
||||
|
||||
FMLLaunchHandler.configureForServerLaunch(classLoader, this);
|
||||
FMLLaunchHandler.appendCoreMods();
|
||||
|
|
|
@ -66,6 +66,8 @@ import com.google.common.collect.Maps;
|
|||
import com.google.common.collect.ObjectArrays;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
|
||||
public class CoreModManager {
|
||||
private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod");
|
||||
|
@ -214,6 +216,16 @@ public class CoreModManager {
|
|||
{
|
||||
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");
|
||||
try
|
||||
|
|
|
@ -21,6 +21,9 @@ package net.minecraftforge.fml.relauncher;
|
|||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
|
||||
public class ServerLaunchWrapper {
|
||||
|
||||
/**
|
||||
|
@ -38,6 +41,11 @@ public class ServerLaunchWrapper {
|
|||
|
||||
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;
|
||||
try
|
||||
{
|
||||
|
|
|
@ -21,21 +21,21 @@ package net.minecraftforge.server.console;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import jline.console.completer.Completer;
|
||||
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 final DedicatedServer server;
|
||||
|
||||
|
@ -45,10 +45,9 @@ public final class ConsoleCommandCompleter implements Completer
|
|||
}
|
||||
|
||||
@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;
|
||||
if (buffer.isEmpty() || buffer.charAt(0) != '/')
|
||||
{
|
||||
|
@ -61,44 +60,19 @@ public final class ConsoleCommandCompleter implements Completer
|
|||
}
|
||||
|
||||
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
|
||||
{
|
||||
List<String> completions = tabComplete.get();
|
||||
Collections.sort(completions);
|
||||
if (prefix)
|
||||
for (String completion : tabComplete.get())
|
||||
{
|
||||
candidates.addAll(completions);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (String completion : completions)
|
||||
if (!completion.isEmpty())
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -108,8 +82,6 @@ public final class ConsoleCommandCompleter implements Completer
|
|||
{
|
||||
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;
|
||||
|
||||
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.util.text.TextFormatting;
|
||||
import net.minecraftforge.server.console.log4j.TerminalConsoleAppender;
|
||||
import org.jline.reader.EndOfFileException;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
public final class TerminalHandler
|
||||
{
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
private TerminalHandler()
|
||||
{
|
||||
}
|
||||
|
||||
public static boolean handleCommands(DedicatedServer server)
|
||||
{
|
||||
final ConsoleReader reader = TerminalConsoleAppender.getReader();
|
||||
if (reader != null)
|
||||
{
|
||||
TerminalConsoleAppender.setFormatter(new ConsoleFormatter());
|
||||
reader.addCompleter(new ConsoleCommandCompleter(server));
|
||||
final Terminal terminal = TerminalConsoleAppender.getTerminal();
|
||||
if (terminal == null)
|
||||
return false;
|
||||
|
||||
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;
|
||||
while (!server.isServerStopped() && server.isServerRunning())
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
return false;
|
||||
server.initiateShutdown();
|
||||
}
|
||||
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"?>
|
||||
<Configuration status="warn" packages="com.mojang.util,net.minecraftforge.server.console.log4j">
|
||||
<Configuration status="warn" packages="net.minecraftforge.server.terminalconsole.util">
|
||||
<Appenders>
|
||||
<TerminalConsole name="Console">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
|
||||
</TerminalConsole>
|
||||
<!-- Keep a console appender open so log4j2 doesn't close our main out stream if we redirect System.out to the logger -->
|
||||
<Console name="SysOut" target="SYSTEM_OUT"/>
|
||||
<Console name="SysOut" target="SYSTEM_OUT">
|
||||
<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>
|
||||
</Console>
|
||||
<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>
|
||||
<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"/>
|
||||
|
@ -33,9 +43,9 @@
|
|||
<filters>
|
||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||
</filters>
|
||||
<AppenderRef ref="Console" level="info"/>
|
||||
<AppenderRef ref="File" level="info"/>
|
||||
<AppenderRef ref="SysOut" level="info"/>
|
||||
<AppenderRef ref="ServerGuiConsole" level="info"/>
|
||||
<AppenderRef ref="File" level="info"/>
|
||||
<AppenderRef ref="DebugFile"/>
|
||||
</Root>
|
||||
</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