├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java-templates │ └── com │ │ └── github │ │ └── games647 │ │ └── colorconsole │ │ └── sponge │ │ └── PomData.java ├── java │ └── com │ │ └── github │ │ └── games647 │ │ └── colorconsole │ │ ├── bukkit │ │ ├── ColorConsoleBukkit.java │ │ └── ColorPluginAppender.java │ │ ├── bungee │ │ ├── ColorConsoleBungee.java │ │ ├── ColorLogFormatter.java │ │ └── ColorPluginAppender.java │ │ ├── common │ │ ├── ColorAppender.java │ │ ├── CommonFormatter.java │ │ ├── ConsoleConfig.java │ │ ├── Log4JInstaller.java │ │ ├── LoggingLevel.java │ │ └── PlatformPlugin.java │ │ └── sponge │ │ ├── ColorConsoleSponge.java │ │ └── SpongeAppender.java └── resources │ ├── bungee.yml │ ├── config.yml │ └── plugin.yml └── test └── java └── com └── github └── games647 └── colorconsole └── common ├── CommonFormatterTest.java └── Log4JInstallerTest.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | ignore: 8 | # Ignore log4j, because it's a provided library 9 | - dependency-name: org.apache.logging.log4j:log4j-core 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # NetBeans 7 | nbproject/ 8 | nb-configuration.xml 9 | 10 | # IntelliJ 11 | *.iml 12 | *.ipr 13 | *.iws 14 | .idea/ 15 | 16 | # Maven 17 | target/ 18 | pom.xml.versionsBackup 19 | 20 | # Gradle 21 | .gradle 22 | 23 | # Ignore Gradle GUI config 24 | gradle-app.setting 25 | 26 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 27 | !gradle-wrapper.jar 28 | 29 | # various other potential build files 30 | build/ 31 | bin/ 32 | dist/ 33 | manifest.mf 34 | *.log 35 | 36 | # Vim 37 | .*.sw[a-p] 38 | 39 | # virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml 40 | hs_err_pid* 41 | 42 | # Mac filesystem dust 43 | .DS_Store 44 | 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use https://travis-ci.org/ for automatic testing 2 | 3 | # speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ 4 | sudo: false 5 | 6 | # This is a java project 7 | language: java 8 | 9 | # Compile the project and run unit tests 10 | script: mvn test -B 11 | 12 | jdk: 13 | - oraclejdk8 14 | - openjdk11 15 | 16 | # Cache Maven dependencies 17 | cache: 18 | directories: 19 | - '$HOME/.m2/repository' 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ColorConsole 2 | 3 | ![colorful log example](https://www.spigotmc.org/attachments/upload_2017-5-25_13-18-54-png.243110/) 4 | 5 | ## Description 6 | 7 | This lightweight plugin to make your console more colorful. It colorize the message depending on the log level. This 8 | means that important error messages will be printed in a red color. This gives you and your administrators a better 9 | overview about what's happening on your server. 10 | 11 | ## Features 12 | 13 | * Lightweight 14 | * Different colors for different log levels 15 | * Ignores specified log messages 16 | * Custom logFormat 17 | * Colorize plugin tags (customizable) 18 | * Removes color from plugins if you want to 19 | * Supports all versions above 1.8.8+ 20 | 21 | ## Supports 22 | 23 | * BungeeCord/Waterfall 24 | * SpongeForge 25 | * Spigot/Paper 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.games647 6 | 7 | colorconsole 8 | jar 9 | 10 | ColorConsole 11 | 3.0.0 12 | 13 | https://dev.bukkit.org/bukkit-plugins/colorconsole/ 14 | 15 | Print colorful console messages depending on the logging level 16 | 17 | 18 | 19 | UTF-8 20 | 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | install 27 | 28 | ${project.name} 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-shade-plugin 34 | 3.2.4 35 | 36 | false 37 | true 38 | 39 | 40 | org.fusesource.jansi:jansi 41 | 42 | 43 | META-INF/** 44 | 45 | 46 | 47 | true 48 | 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.codehaus.mojo 61 | templating-maven-plugin 62 | 1.0.0 63 | 64 | 65 | filter-src 66 | 67 | filter-sources 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | src/main/resources 77 | 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | spigot-repo 87 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 88 | 89 | 90 | 91 | 92 | sponge-repo 93 | https://repo.spongepowered.org/maven 94 | 95 | 96 | 97 | bungeecord-repo 98 | https://oss.sonatype.org/content/repositories/snapshots 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.spigotmc 106 | spigot-api 107 | 1.16.5-R0.1-SNAPSHOT 108 | provided 109 | 110 | 111 | 112 | 113 | net.md-5 114 | bungeecord-api 115 | 1.16-R0.3 116 | provided 117 | 118 | 119 | 120 | org.spongepowered 121 | spongeapi 122 | 7.4.0 123 | provided 124 | 125 | 126 | 127 | org.fusesource.jansi 128 | jansi 129 | 2.4.0 130 | 131 | 132 | 133 | 134 | org.apache.logging.log4j 135 | log4j-core 136 | 137 | 2.1 138 | 139 | provided 140 | 141 | 142 | 143 | junit 144 | junit 145 | 4.13.2 146 | test 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/main/java-templates/com/github/games647/colorconsole/sponge/PomData.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.sponge; 2 | 3 | class PomData { 4 | 5 | public static final String ARTIFACT_ID = "${project.artifactId}"; 6 | public static final String NAME = "${project.name}"; 7 | public static final String VERSION = "${project.version}"; 8 | public static final String URL = "${project.url}"; 9 | public static final String DESCRIPTION = "${project.description}"; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/bukkit/ColorConsoleBukkit.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.bukkit; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | import com.github.games647.colorconsole.common.ConsoleConfig; 5 | import com.github.games647.colorconsole.common.Log4JInstaller; 6 | import com.github.games647.colorconsole.common.LoggingLevel; 7 | import com.github.games647.colorconsole.common.PlatformPlugin; 8 | 9 | import java.io.Serializable; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | import java.util.logging.Level; 13 | 14 | import org.apache.logging.log4j.core.Appender; 15 | import org.apache.logging.log4j.core.Layout; 16 | import org.bukkit.Bukkit; 17 | import org.bukkit.configuration.ConfigurationSection; 18 | import org.bukkit.configuration.file.FileConfiguration; 19 | import org.bukkit.plugin.java.JavaPlugin; 20 | 21 | public class ColorConsoleBukkit extends JavaPlugin implements PlatformPlugin { 22 | 23 | private static final String TERMINAL_NAME = "TerminalConsole"; 24 | private static final String CATSERVER_TERMINAL = "Console"; 25 | 26 | 27 | private final Log4JInstaller installer = new Log4JInstaller(); 28 | 29 | private Layout oldLayout; 30 | 31 | @Override 32 | public void onLoad() { 33 | //try to run it as early as possible 34 | saveDefaultConfig(); 35 | ConsoleConfig configuration = loadConfiguration(); 36 | 37 | installLogFormat(configuration); 38 | } 39 | 40 | @Override 41 | public void onDisable() { 42 | revertLogFormat(); 43 | } 44 | 45 | @Override 46 | public void installLogFormat(ConsoleConfig configuration) { 47 | try { 48 | oldLayout = installer.installLog4JFormat(this, getTerminalName(), configuration); 49 | } catch (ReflectiveOperationException reflectiveEx) { 50 | getLogger().log(Level.WARNING, "Failed to install log format", reflectiveEx); 51 | } 52 | } 53 | 54 | @Override 55 | public ColorAppender createAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol) { 56 | return new ColorPluginAppender(oldAppender, hideMessages, truncateCol); 57 | } 58 | 59 | @Override 60 | public void revertLogFormat() { 61 | try { 62 | installer.revertLog4JFormat(getTerminalName(), oldLayout); 63 | } catch (ReflectiveOperationException ex) { 64 | getLogger().log(Level.WARNING, "Cannot revert log format", ex); 65 | } 66 | } 67 | 68 | @Override 69 | public Path getPluginFolder() { 70 | return getDataFolder().toPath(); 71 | } 72 | 73 | @Override 74 | public ConsoleConfig loadConfiguration() { 75 | FileConfiguration bukkitConfig = getConfig(); 76 | 77 | ConsoleConfig consoleConfig = new ConsoleConfig(); 78 | consoleConfig.setLogFormat(bukkitConfig.getString("logFormat")); 79 | consoleConfig.setDateStyle(bukkitConfig.getString("dateStyle")); 80 | 81 | consoleConfig.getLevelColors().clear(); 82 | if (bukkitConfig.getBoolean("colorLoggingLevel")) { 83 | ConfigurationSection levelSection = bukkitConfig.getConfigurationSection("Level"); 84 | for (LoggingLevel level : LoggingLevel.values()) { 85 | consoleConfig.getLevelColors().put(level, levelSection.getString(level.name(), "")); 86 | } 87 | } 88 | 89 | consoleConfig.getPluginColors().clear(); 90 | if (bukkitConfig.getBoolean("colorPluginTag")) { 91 | ConfigurationSection pluginSection = bukkitConfig.getConfigurationSection("Plugin"); 92 | consoleConfig.setDefaultPluginColor(pluginSection.getString(ConsoleConfig.DEFAULT_PLUGIN_KEY)); 93 | for (String pluginKey : pluginSection.getKeys(false)) { 94 | consoleConfig.getPluginColors().put(pluginKey, pluginSection.getString(pluginKey)); 95 | } 96 | } 97 | 98 | consoleConfig.getHideMessages().clear(); 99 | consoleConfig.getHideMessages().addAll(bukkitConfig.getStringList("hide-messages")); 100 | 101 | consoleConfig.setTruncateColor(bukkitConfig.getBoolean("truncateColor")); 102 | return consoleConfig; 103 | } 104 | 105 | private String getTerminalName() { 106 | if (Bukkit.getVersion().contains("Cat")) { 107 | return CATSERVER_TERMINAL; 108 | } 109 | 110 | return TERMINAL_NAME; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/bukkit/ColorPluginAppender.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.bukkit; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | import com.google.common.collect.Sets; 5 | 6 | import java.util.Collection; 7 | import java.util.Set; 8 | import java.util.stream.Stream; 9 | 10 | import org.apache.logging.log4j.core.Appender; 11 | import org.apache.logging.log4j.core.LogEvent; 12 | import org.apache.logging.log4j.message.Message; 13 | import org.apache.logging.log4j.message.SimpleMessage; 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.plugin.Plugin; 16 | 17 | import static java.util.stream.Collectors.toSet; 18 | 19 | public class ColorPluginAppender extends ColorAppender { 20 | 21 | private static final Set disabledPrefix = Sets.newHashSet( 22 | "net.minecraft", 23 | "Minecraft", 24 | "com.mojang", 25 | "com.sk89q", 26 | "ru.tehkode", 27 | "Minecraft.AWE" 28 | ); 29 | 30 | private final boolean isVanillaAppender; 31 | 32 | public ColorPluginAppender(Appender oldAppender, Collection hideMessage, boolean truncateColor) { 33 | super(oldAppender, hideMessage, truncateColor); 34 | this.isVanillaAppender = "QueueLogAppender".equals(oldAppender.getClass().getSimpleName()); 35 | } 36 | 37 | @Override 38 | public LogEvent onAppend(LogEvent logEvent) { 39 | String oldMessage = logEvent.getMessage().getFormattedMessage(); 40 | 41 | String prefix = ""; 42 | if (!logEvent.getLoggerName().isEmpty()) { 43 | // this only necessary in Bukkit for console messages like commands 44 | prefix = '[' + logEvent.getLoggerName() + "] "; 45 | } 46 | 47 | //PaperSpigot append prefix 48 | if (!isVanillaAppender 49 | && disabledPrefix.stream().noneMatch(disabled -> logEvent.getLoggerName().startsWith(disabled))) { 50 | oldMessage = prefix + oldMessage; 51 | } 52 | 53 | String message = formatter.colorizePluginTag(oldMessage); 54 | Message newMessage = new SimpleMessage(message); 55 | return clone(logEvent, logEvent.getLoggerName(), newMessage); 56 | } 57 | 58 | @Override 59 | protected Collection loadPluginNames() { 60 | return Stream.of(Bukkit.getPluginManager().getPlugins()) 61 | .map(Plugin::getName) 62 | .collect(toSet()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/bungee/ColorConsoleBungee.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.bungee; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | import com.github.games647.colorconsole.common.ConsoleConfig; 5 | import com.github.games647.colorconsole.common.Log4JInstaller; 6 | import com.github.games647.colorconsole.common.LoggingLevel; 7 | import com.github.games647.colorconsole.common.PlatformPlugin; 8 | 9 | import java.io.IOException; 10 | import java.io.Serializable; 11 | import java.nio.file.Path; 12 | import java.util.Collection; 13 | import java.util.EnumMap; 14 | import java.util.logging.Formatter; 15 | import java.util.logging.Handler; 16 | import java.util.logging.Level; 17 | import java.util.logging.Logger; 18 | 19 | import net.md_5.bungee.api.ProxyServer; 20 | import net.md_5.bungee.api.plugin.Plugin; 21 | import net.md_5.bungee.config.Configuration; 22 | import net.md_5.bungee.config.ConfigurationProvider; 23 | import net.md_5.bungee.config.YamlConfiguration; 24 | 25 | import org.apache.logging.log4j.core.Appender; 26 | import org.apache.logging.log4j.core.Layout; 27 | 28 | public class ColorConsoleBungee extends Plugin implements PlatformPlugin { 29 | 30 | private static final String TERMINAL_NAME = "TerminalConsole"; 31 | 32 | private final Log4JInstaller installer = new Log4JInstaller(); 33 | private Layout oldLayout; 34 | 35 | @Override 36 | public void onEnable() { 37 | ConsoleConfig configuration; 38 | try { 39 | saveDefaultConfig(); 40 | configuration = loadConfiguration(); 41 | } catch (IOException ioEx) { 42 | getLogger().log(Level.SEVERE, "Unable to load configuration", ioEx); 43 | return; 44 | } 45 | 46 | installLogFormat(configuration); 47 | } 48 | 49 | @Override 50 | public void onDisable() { 51 | revertLogFormat(); 52 | } 53 | 54 | @Override 55 | public void installLogFormat(ConsoleConfig config) { 56 | if (isWaterfallLog4J()) { 57 | try { 58 | oldLayout = installer.installLog4JFormat(this, TERMINAL_NAME, config); 59 | } catch (ReflectiveOperationException reflectiveEx) { 60 | getLogger().log(Level.WARNING, "Cannot install log format", reflectiveEx); 61 | } 62 | } else { 63 | getLogger().info("Waterfall Log4J not detected. Falling back to vanilla logging"); 64 | ProxyServer bungee = ProxyServer.getInstance(); 65 | Logger bungeeLogger = bungee.getLogger(); 66 | 67 | Handler[] handlers = bungeeLogger.getHandlers(); 68 | for (Handler handler : handlers) { 69 | Formatter oldFormatter = handler.getFormatter(); 70 | 71 | EnumMap levelColors = config.getLevelColors(); 72 | Collection hideMessages = config.getHideMessages(); 73 | boolean truncateCol = config.isTruncateColor(); 74 | ColorLogFormatter newForm = new ColorLogFormatter(oldFormatter, levelColors, hideMessages, truncateCol); 75 | 76 | newForm.initPluginColors(config.getPluginColors(), config.getDefaultPluginColor()); 77 | handler.setFormatter(newForm); 78 | } 79 | } 80 | } 81 | 82 | @Override 83 | public ColorAppender createAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol) { 84 | return new ColorPluginAppender(oldAppender, hideMessages, truncateCol); 85 | } 86 | 87 | @Override 88 | public void revertLogFormat() { 89 | if (isWaterfallLog4J()) { 90 | try { 91 | installer.revertLog4JFormat(TERMINAL_NAME, oldLayout); 92 | } catch (ReflectiveOperationException reflectiveEx) { 93 | getLogger().log(Level.WARNING, "Cannot revert logging format", reflectiveEx); 94 | } 95 | } else { 96 | ProxyServer bungee = ProxyServer.getInstance(); 97 | Logger bungeeLogger = bungee.getLogger(); 98 | 99 | Handler[] handlers = bungeeLogger.getHandlers(); 100 | for (Handler handler : handlers) { 101 | Formatter formatter = handler.getFormatter(); 102 | if (formatter instanceof ColorLogFormatter) { 103 | handler.setFormatter(((ColorLogFormatter) formatter).getOldFormatter()); 104 | } 105 | } 106 | } 107 | } 108 | 109 | @Override 110 | public Path getPluginFolder() { 111 | return getDataFolder().toPath(); 112 | } 113 | 114 | @Override 115 | public ConsoleConfig loadConfiguration() throws IOException { 116 | Path configPath = getPluginFolder().resolve(CONFIG_NAME); 117 | 118 | ConfigurationProvider yamlProvider = ConfigurationProvider.getProvider(YamlConfiguration.class); 119 | Configuration bungeeConfig = yamlProvider.load(configPath.toFile()); 120 | 121 | ConsoleConfig consoleConfig = new ConsoleConfig(); 122 | consoleConfig.setLogFormat(bungeeConfig.getString("logFormat")); 123 | consoleConfig.setDateStyle(bungeeConfig.getString("dateStyle")); 124 | 125 | consoleConfig.getLevelColors().clear(); 126 | if (bungeeConfig.getBoolean("colorLoggingLevel")) { 127 | Configuration levelSection = bungeeConfig.getSection("Level"); 128 | for (LoggingLevel level : LoggingLevel.values()) { 129 | consoleConfig.getLevelColors().put(level, levelSection.getString(level.name(), "")); 130 | } 131 | } 132 | 133 | consoleConfig.getPluginColors().clear(); 134 | if (bungeeConfig.getBoolean("colorPluginTag")) { 135 | Configuration pluginSection = bungeeConfig.getSection("Plugin"); 136 | consoleConfig.setDefaultPluginColor(pluginSection.getString(ConsoleConfig.DEFAULT_PLUGIN_KEY)); 137 | for (String pluginKey : pluginSection.getKeys()) { 138 | consoleConfig.getPluginColors().put(pluginKey, pluginSection.getString(pluginKey)); 139 | } 140 | } 141 | 142 | consoleConfig.getHideMessages().clear(); 143 | consoleConfig.getHideMessages().addAll(bungeeConfig.getStringList("hide-messages")); 144 | 145 | consoleConfig.setTruncateColor(bungeeConfig.getBoolean("truncateColor")); 146 | return consoleConfig; 147 | } 148 | 149 | private boolean isWaterfallLog4J() { 150 | try { 151 | Class.forName("io.github.waterfallmc.waterfall.log4j.WaterfallLogger"); 152 | return true; 153 | } catch (ClassNotFoundException e) { 154 | return false; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/bungee/ColorLogFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.bungee; 2 | 3 | import com.github.games647.colorconsole.common.CommonFormatter; 4 | import com.github.games647.colorconsole.common.LoggingLevel; 5 | 6 | import java.io.PrintWriter; 7 | import java.io.StringWriter; 8 | import java.time.Instant; 9 | import java.time.format.DateTimeFormatter; 10 | import java.util.Collection; 11 | import java.util.EnumMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.logging.Formatter; 15 | import java.util.logging.Level; 16 | import java.util.logging.LogRecord; 17 | 18 | import net.md_5.bungee.api.ProxyServer; 19 | 20 | import static java.util.stream.Collectors.toSet; 21 | 22 | public class ColorLogFormatter extends Formatter { 23 | 24 | private final Formatter oldFormatter; 25 | private final DateTimeFormatter date = DateTimeFormatter.ofPattern("HH:mm:ss"); 26 | 27 | private final EnumMap levelColors; 28 | private final CommonFormatter formatter; 29 | 30 | public ColorLogFormatter(Formatter oldFormatter, EnumMap levels, 31 | Collection hideMessages, boolean truncateColor) { 32 | this.oldFormatter = oldFormatter; 33 | this.levelColors = levels; 34 | this.formatter = new CommonFormatter(hideMessages, truncateColor); 35 | } 36 | 37 | @Override 38 | public String format(LogRecord record) { 39 | if (formatter.shouldIgnore(record.getMessage())) { 40 | return ""; 41 | } 42 | 43 | StringBuilder formatted = new StringBuilder(); 44 | String message = oldFormatter.formatMessage(record); 45 | 46 | String levelColor = levelColors.getOrDefault(translateToLog4JName(record.getLevel()), ""); 47 | formatted.append(levelColor); 48 | 49 | formatted.append(date.format(Instant.ofEpochMilli(record.getMillis()))); 50 | formatted.append(" ["); 51 | formatted.append(record.getLevel().getName()); 52 | formatted.append("] "); 53 | 54 | formatted.append(formatter.getReset()); 55 | 56 | formatted.append(formatter.colorizePluginTag(message)); 57 | 58 | formatted.append('\n'); 59 | if (record.getThrown() != null) { 60 | StringWriter writer = new StringWriter(); 61 | record.getThrown().printStackTrace(new PrintWriter(writer)); 62 | formatted.append(writer); 63 | } 64 | 65 | return formatted.toString(); 66 | } 67 | 68 | public Formatter getOldFormatter() { 69 | return oldFormatter; 70 | } 71 | 72 | private LoggingLevel translateToLog4JName(Level level) { 73 | if (level == Level.SEVERE) { 74 | return LoggingLevel.ERROR; 75 | } else if (level == Level.WARNING) { 76 | return LoggingLevel.WARN; 77 | } else if (level == Level.INFO) { 78 | return LoggingLevel.INFO; 79 | } else if (level == Level.CONFIG) { 80 | return LoggingLevel.DEBUG; 81 | } else { 82 | return LoggingLevel.TRACE; 83 | } 84 | } 85 | 86 | private Set loadPluginNames() { 87 | return ProxyServer.getInstance().getPluginManager().getPlugins().stream() 88 | .map(plugin -> plugin.getDescription().getName()) 89 | .collect(toSet()); 90 | } 91 | 92 | public void initPluginColors(Map pluginColors, String def) { 93 | formatter.initPluginColors(loadPluginNames(), pluginColors, def); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/bungee/ColorPluginAppender.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.bungee; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | 5 | import java.util.Collection; 6 | 7 | import net.md_5.bungee.api.ProxyServer; 8 | import net.md_5.bungee.api.plugin.Plugin; 9 | import net.md_5.bungee.api.plugin.PluginDescription; 10 | 11 | import org.apache.logging.log4j.core.Appender; 12 | import org.apache.logging.log4j.core.LogEvent; 13 | import org.apache.logging.log4j.message.Message; 14 | import org.apache.logging.log4j.message.SimpleMessage; 15 | 16 | import static java.util.stream.Collectors.toSet; 17 | 18 | public class ColorPluginAppender extends ColorAppender { 19 | 20 | private static final String PROXY_PREFIX = "BungeeCord"; 21 | 22 | public ColorPluginAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol) { 23 | super(oldAppender, hideMessages, truncateCol); 24 | } 25 | 26 | @Override 27 | public LogEvent onAppend(LogEvent logEvent) { 28 | String message = logEvent.getMessage().getFormattedMessage(); 29 | String loggerName = logEvent.getLoggerName(); 30 | 31 | //old message + potential prefix and color codes 32 | StringBuilder msgBuilder = new StringBuilder(message.length() + loggerName.length() + 10); 33 | if (!PROXY_PREFIX.equals(loggerName)) { 34 | msgBuilder.append('[') 35 | .append(formatter.colorizePluginName(loggerName)) 36 | .append("] "); 37 | message = msgBuilder.append(message).toString(); 38 | } 39 | 40 | Message newMessage = new SimpleMessage(message); 41 | return clone(logEvent, loggerName, newMessage); 42 | } 43 | 44 | @Override 45 | protected Collection loadPluginNames() { 46 | return ProxyServer.getInstance().getPluginManager().getPlugins().stream() 47 | .map(Plugin::getDescription) 48 | .map(PluginDescription::getName) 49 | .collect(toSet()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/ColorAppender.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | 7 | import org.apache.logging.log4j.core.Appender; 8 | import org.apache.logging.log4j.core.LogEvent; 9 | import org.apache.logging.log4j.core.appender.AbstractAppender; 10 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 11 | import org.apache.logging.log4j.message.Message; 12 | 13 | public abstract class ColorAppender extends AbstractAppender { 14 | 15 | private static final Method loggerClassGetter; 16 | private boolean disabled = loggerClassGetter == null; 17 | 18 | static { 19 | Method classGetter = null; 20 | for (Method method : LogEvent.class.getDeclaredMethods()) { 21 | String methodName = method.getName(); 22 | if ("getLoggerFqcn".equalsIgnoreCase(methodName) 23 | || "getFQCN".equalsIgnoreCase(methodName)) { 24 | classGetter = method; 25 | method.setAccessible(true); 26 | break; 27 | } 28 | } 29 | 30 | loggerClassGetter = classGetter; 31 | } 32 | 33 | protected final Appender oldAppender; 34 | protected final CommonFormatter formatter; 35 | 36 | protected ColorAppender(Appender oldAppender, Collection hideMessages, boolean truncateColor) { 37 | super(oldAppender.getName(), null, oldAppender.getLayout()); 38 | 39 | this.oldAppender = oldAppender; 40 | this.formatter = new CommonFormatter(hideMessages, truncateColor); 41 | } 42 | 43 | public void initPluginColors(Map configColors, String def) { 44 | formatter.initPluginColors(loadPluginNames(), configColors, def); 45 | } 46 | 47 | @Override 48 | public final void append(LogEvent logEvent) { 49 | if (oldAppender.isStarted()) { 50 | String oldMessage = logEvent.getMessage().getFormattedMessage(); 51 | if (formatter.shouldIgnore(oldMessage)) { 52 | return; 53 | } 54 | 55 | oldAppender.append(onAppend(logEvent)); 56 | } 57 | } 58 | 59 | public LogEvent onAppend(LogEvent logEvent) { 60 | String newLoggerName = formatter.colorizePluginName(logEvent.getLoggerName()); 61 | return clone(logEvent, newLoggerName, logEvent.getMessage()); 62 | } 63 | 64 | protected abstract Collection loadPluginNames(); 65 | 66 | protected LogEvent clone(LogEvent oldEvent, String loggerName, Message message) { 67 | String className = null; 68 | if (!disabled) { 69 | try { 70 | className = (String) loggerClassGetter.invoke(oldEvent); 71 | } catch (ReflectiveOperationException refEx) { 72 | //if this method cannot be found then the other methods wouldn't work neither 73 | disabled = true; 74 | } 75 | } 76 | 77 | return new Log4jLogEvent(loggerName, oldEvent.getMarker(), className 78 | , oldEvent.getLevel(), message, oldEvent.getThrown()); 79 | } 80 | 81 | public Appender getOldAppender() { 82 | return oldAppender; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/CommonFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.collect.ImmutableMap.Builder; 5 | import com.google.common.collect.ImmutableSet; 6 | 7 | import io.netty.util.internal.ThreadLocalRandom; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.regex.Pattern; 14 | 15 | import org.fusesource.jansi.Ansi; 16 | import org.fusesource.jansi.Ansi.Attribute; 17 | import org.fusesource.jansi.Ansi.Color; 18 | import org.fusesource.jansi.AnsiRenderer.Code; 19 | 20 | public class CommonFormatter { 21 | 22 | //copied from AnsiEscape in order to provide compatibility with older Minecraft versions 23 | private static final String CSI = "\u001b["; 24 | private static final char SUFFIX = 'm'; 25 | 26 | private static final Pattern TAG_PATTERN = Pattern.compile("^\\[.+\\].*$"); 27 | 28 | private final String reset = Ansi.ansi().a(Attribute.RESET).toString(); 29 | 30 | private final Set ignoreMessages; 31 | private final boolean truncateColor; 32 | private Map pluginColors; 33 | 34 | public CommonFormatter(Collection ignoreMessages, boolean truncateColor) { 35 | this.ignoreMessages = ImmutableSet.copyOf(ignoreMessages); 36 | this.truncateColor = truncateColor; 37 | } 38 | 39 | public boolean shouldIgnore(String message) { 40 | for (String ignore : ignoreMessages) { 41 | if (message.contains(ignore)) { 42 | return true; 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | 49 | public void initPluginColors(Iterable plugins, Map configColors, String def) { 50 | Color[] colors = Color.values(); 51 | // remove black, because it's often hard to read 52 | colors = Arrays.copyOfRange(colors, 1, colors.length); 53 | 54 | Builder colorBuilder = ImmutableMap.builder(); 55 | for (String plugin : plugins) { 56 | String styleCode = configColors.getOrDefault(plugin, def); 57 | if ("random".equalsIgnoreCase(styleCode)) { 58 | //ignore default 59 | styleCode = colors[ThreadLocalRandom.current().nextInt(colors.length - 1)].name(); 60 | } 61 | 62 | colorBuilder.put(plugin, format(styleCode)); 63 | } 64 | 65 | this.pluginColors = colorBuilder.build(); 66 | } 67 | 68 | public String colorizePluginTag(String message) { 69 | String newMessage = message; 70 | if (!TAG_PATTERN.matcher(message).matches()) { 71 | return newMessage; 72 | } 73 | 74 | String startingColorCode = ""; 75 | if (message.startsWith(CSI)) { 76 | int endColor = message.indexOf(SUFFIX); 77 | 78 | newMessage = message.substring(endColor + 1); 79 | if (!truncateColor) { 80 | startingColorCode = message.substring(0, endColor + 1); 81 | } 82 | } 83 | 84 | int startTag = newMessage.indexOf('[') + 1; 85 | int endTag = newMessage.indexOf(']', startTag); 86 | 87 | String pluginName = colorizePluginName(newMessage.substring(startTag, endTag)); 88 | return '[' + pluginName + ']' + startingColorCode + newMessage.substring(endTag + 1) + reset; 89 | } 90 | 91 | public String colorizePluginName(String pluginName) { 92 | String pluginColor = pluginColors.getOrDefault(pluginName, ""); 93 | return pluginColor + pluginName + reset; 94 | } 95 | 96 | private String format(String keyCode) { 97 | String[] formatParts = keyCode.split(" "); 98 | Ansi ansi = Ansi.ansi(); 99 | for (String format : formatParts) { 100 | for (Code ansiCode : Code.values()) { 101 | if (ansiCode.name().equalsIgnoreCase(format)) { 102 | if (ansiCode.isAttribute()) { 103 | ansi.a(ansiCode.getAttribute()); 104 | } else if (ansiCode.isBackground()) { 105 | ansi.bg(ansiCode.getColor()); 106 | } else { 107 | ansi.fg(ansiCode.getColor()); 108 | } 109 | } 110 | } 111 | 112 | if ("blink".equalsIgnoreCase(format)) { 113 | ansi.a(Attribute.BLINK_SLOW); 114 | continue; 115 | } 116 | 117 | if ("strikethrough".equalsIgnoreCase(format)) { 118 | ansi.a(Attribute.STRIKETHROUGH_ON); 119 | continue; 120 | } 121 | 122 | if ("hidden".equalsIgnoreCase(format)) { 123 | ansi.a(Attribute.CONCEAL_OFF); 124 | continue; 125 | } 126 | 127 | if ("dim".equalsIgnoreCase(format)) { 128 | ansi.a(Attribute.INTENSITY_FAINT); 129 | continue; 130 | } 131 | 132 | if ("reverse".equalsIgnoreCase(format)) { 133 | ansi.a(Attribute.NEGATIVE_ON); 134 | } 135 | } 136 | 137 | return ansi.toString(); 138 | } 139 | 140 | public String getReset() { 141 | return reset; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/ConsoleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.EnumMap; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class ConsoleConfig { 11 | 12 | public static String DEFAULT_PLUGIN_KEY = "Default"; 13 | 14 | private String logFormat; 15 | private String dateStyle; 16 | 17 | private final EnumMap levelColors = new EnumMap<>(LoggingLevel.class); 18 | 19 | private String defaultPluginColor; 20 | private final Map pluginColors = new HashMap<>(); 21 | 22 | private final List hideMessages = new ArrayList<>(); 23 | private boolean truncateColor; 24 | 25 | public String getLogFormat() { 26 | return logFormat; 27 | } 28 | 29 | public EnumMap getLevelColors() { 30 | return levelColors; 31 | } 32 | 33 | public String getDefaultPluginColor() { 34 | return defaultPluginColor; 35 | } 36 | 37 | public Map getPluginColors() { 38 | return pluginColors; 39 | } 40 | 41 | public Collection getHideMessages() { 42 | return hideMessages; 43 | } 44 | 45 | public String getDateStyle() { 46 | return dateStyle; 47 | } 48 | 49 | public boolean isTruncateColor() { 50 | return truncateColor; 51 | } 52 | 53 | public void setLogFormat(String logFormat) { 54 | this.logFormat = logFormat; 55 | } 56 | 57 | public void setDateStyle(String dateStyle) { 58 | this.dateStyle = dateStyle; 59 | } 60 | 61 | public void setDefaultPluginColor(String defaultPluginColor) { 62 | this.defaultPluginColor = defaultPluginColor; 63 | } 64 | 65 | public void setTruncateColor(boolean truncateColor) { 66 | this.truncateColor = truncateColor; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/Log4JInstaller.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Joiner; 5 | 6 | import java.io.Serializable; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.nio.charset.Charset; 10 | import java.util.Collection; 11 | import java.util.EnumMap; 12 | import java.util.Map; 13 | 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.core.Appender; 16 | import org.apache.logging.log4j.core.Layout; 17 | import org.apache.logging.log4j.core.Logger; 18 | import org.apache.logging.log4j.core.LoggerContext; 19 | import org.apache.logging.log4j.core.config.Configuration; 20 | import org.apache.logging.log4j.core.config.DefaultConfiguration; 21 | import org.apache.logging.log4j.core.layout.PatternLayout; 22 | import org.apache.logging.log4j.core.pattern.RegexReplacement; 23 | 24 | public class Log4JInstaller { 25 | 26 | public PatternLayout createLayout(String logFormat) throws ReflectiveOperationException { 27 | try { 28 | Method builder = PatternLayout.class 29 | .getDeclaredMethod("createLayout", String.class, Configuration.class, RegexReplacement.class 30 | , String.class, String.class); 31 | 32 | return (PatternLayout) builder.invoke(null, logFormat, new DefaultConfiguration(), null 33 | , Charset.defaultCharset().name(), "true"); 34 | } catch (NoSuchMethodException methodEx) { 35 | return PatternLayout.newBuilder() 36 | .withCharset(Charset.defaultCharset()) 37 | .withPattern(logFormat) 38 | .withConfiguration(new DefaultConfiguration()) 39 | .withAlwaysWriteExceptions(true) 40 | .build(); 41 | } 42 | } 43 | 44 | public void installAppender(Appender colorAppender, String terminalName) { 45 | Logger rootLogger = (Logger) LogManager.getRootLogger(); 46 | 47 | colorAppender.start(); 48 | 49 | rootLogger.removeAppender(getTerminalAppender(terminalName)); 50 | rootLogger.addAppender(colorAppender); 51 | } 52 | 53 | public void setLayout(Layout layout, Appender terminalAppender) 54 | throws ReflectiveOperationException { 55 | Field field = terminalAppender.getClass().getSuperclass().getDeclaredField("layout"); 56 | field.setAccessible(true); 57 | field.set(terminalAppender, layout); 58 | } 59 | 60 | public Layout installLog4JFormat(PlatformPlugin pl, 61 | String terminalName, ConsoleConfig config) 62 | throws ReflectiveOperationException { 63 | Appender terminalAppender = getTerminalAppender(terminalName); 64 | Layout oldLayout = terminalAppender.getLayout(); 65 | 66 | String logFormat = config.getLogFormat(); 67 | String appenderClass = terminalAppender.getClass().getName(); 68 | if (isMinecrellFormatted(oldLayout, appenderClass)) { 69 | logFormat = logFormat.replace("%msg", "%minecraftFormatting{%msg}"); 70 | } 71 | 72 | logFormat = mapLoggingLevels(logFormat, config.getLevelColors()); 73 | logFormat = formatDate(logFormat, config.getDateStyle()); 74 | 75 | PatternLayout layout = createLayout(logFormat); 76 | setLayout(layout, terminalAppender); 77 | 78 | Collection hideMessages = config.getHideMessages(); 79 | boolean truncateColor = config.isTruncateColor(); 80 | ColorAppender appender = pl.createAppender(terminalAppender, hideMessages, truncateColor); 81 | appender.initPluginColors(config.getPluginColors(), config.getDefaultPluginColor()); 82 | 83 | installAppender(appender, terminalName); 84 | return oldLayout; 85 | } 86 | 87 | public void revertLog4JFormat(String terminalName, Layout oldLayout) 88 | throws ReflectiveOperationException { 89 | Appender terminalAppender = getTerminalAppender(terminalName); 90 | 91 | Logger rootLogger = (Logger) LogManager.getRootLogger(); 92 | ColorAppender colorPluginAppender = null; 93 | for (Appender value : rootLogger.getAppenders().values()) { 94 | if (value instanceof ColorAppender) { 95 | colorPluginAppender = (ColorAppender) value; 96 | break; 97 | } 98 | } 99 | 100 | if (colorPluginAppender != null) { 101 | rootLogger.removeAppender(terminalAppender); 102 | rootLogger.addAppender(colorPluginAppender.getOldAppender()); 103 | } 104 | 105 | setLayout(oldLayout, terminalAppender); 106 | } 107 | 108 | public Appender getTerminalAppender(String terminalName) { 109 | LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 110 | Configuration conf = ctx.getConfiguration(); 111 | return conf.getAppender(terminalName); 112 | } 113 | 114 | @VisibleForTesting 115 | protected boolean isMinecrellFormatted(Layout oldLayout, String appenderClass) { 116 | return oldLayout.toString().contains("minecraftFormatting") || appenderClass.contains("minecrell"); 117 | } 118 | 119 | @VisibleForTesting 120 | protected String formatDate(String logFormat, String dateStyle) { 121 | return logFormat.replaceFirst("(%d)\\{.*?}", "%style{$0}{" + dateStyle + '}'); 122 | } 123 | 124 | @VisibleForTesting 125 | protected String mapLoggingLevels(String logFormat, Map levelColors) { 126 | Map sortedColors = new EnumMap<>(LoggingLevel.class); 127 | sortedColors.putAll(levelColors); 128 | 129 | String levelFormat = Joiner.on(", ").withKeyValueSeparator('=').join(sortedColors); 130 | return logFormat.replace("%level", "%highlight{%level}{" + levelFormat + '}'); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/LoggingLevel.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | public enum LoggingLevel { 4 | 5 | FATAL, 6 | 7 | ERROR, 8 | 9 | WARN, 10 | 11 | INFO, 12 | 13 | DEBUG, 14 | 15 | TRACE 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/common/PlatformPlugin.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Collection; 8 | 9 | import org.apache.logging.log4j.core.Appender; 10 | 11 | public interface PlatformPlugin { 12 | 13 | String CONFIG_NAME = "config.yml"; 14 | 15 | void installLogFormat(ConsoleConfig configuration); 16 | 17 | ColorAppender createAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol); 18 | 19 | //restore the old format 20 | void revertLogFormat(); 21 | 22 | Path getPluginFolder(); 23 | 24 | ConsoleConfig loadConfiguration() throws IOException; 25 | 26 | default void saveDefaultConfig() throws IOException { 27 | Path dataFolder = getPluginFolder(); 28 | if (Files.notExists(dataFolder)) { 29 | Files.createDirectories(dataFolder); 30 | } 31 | 32 | Path configFile = dataFolder.resolve(CONFIG_NAME); 33 | if (Files.notExists(configFile)) { 34 | try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(CONFIG_NAME)) { 35 | if (defaultStream == null) { 36 | return; 37 | } 38 | 39 | Files.copy(defaultStream, configFile); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/sponge/ColorConsoleSponge.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.sponge; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | import com.github.games647.colorconsole.common.ConsoleConfig; 5 | import com.github.games647.colorconsole.common.Log4JInstaller; 6 | import com.github.games647.colorconsole.common.LoggingLevel; 7 | import com.github.games647.colorconsole.common.PlatformPlugin; 8 | import com.google.common.reflect.TypeToken; 9 | import com.google.inject.Inject; 10 | 11 | import java.io.IOException; 12 | import java.io.Serializable; 13 | import java.nio.file.Path; 14 | import java.util.Collection; 15 | import java.util.List; 16 | import java.util.Map.Entry; 17 | 18 | import ninja.leaping.configurate.ConfigurationNode; 19 | import ninja.leaping.configurate.objectmapping.ObjectMappingException; 20 | import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; 21 | 22 | import org.apache.logging.log4j.core.Appender; 23 | import org.apache.logging.log4j.core.Layout; 24 | import org.slf4j.Logger; 25 | import org.spongepowered.api.config.ConfigDir; 26 | import org.spongepowered.api.event.Listener; 27 | import org.spongepowered.api.event.game.state.GamePreInitializationEvent; 28 | import org.spongepowered.api.plugin.Plugin; 29 | 30 | @Plugin(id = PomData.ARTIFACT_ID, name = PomData.NAME, version = PomData.VERSION, 31 | url = PomData.URL, description = PomData.DESCRIPTION) 32 | public class ColorConsoleSponge implements PlatformPlugin { 33 | 34 | //Console is maybe required too? 35 | private static final String TERMINAL_NAME = "MinecraftConsole"; 36 | 37 | private final Path pluginFolder; 38 | private final Logger logger; 39 | 40 | private final Log4JInstaller installer = new Log4JInstaller(); 41 | private Layout oldLayout; 42 | 43 | @Inject 44 | public ColorConsoleSponge(Logger logger, @ConfigDir(sharedRoot = false) Path dataFolder) { 45 | this.pluginFolder = dataFolder; 46 | this.logger = logger; 47 | } 48 | 49 | @Listener 50 | public void onPreInit(GamePreInitializationEvent preInitEvent) { 51 | ConsoleConfig configuration; 52 | try { 53 | saveDefaultConfig(); 54 | configuration = loadConfiguration(); 55 | } catch (IOException ioEx) { 56 | logger.warn("Failed to load configuration file. Canceling plugin setup", ioEx); 57 | return; 58 | } 59 | 60 | installLogFormat(configuration); 61 | } 62 | 63 | @Override 64 | public void installLogFormat(ConsoleConfig configuration) { 65 | try { 66 | oldLayout = installer.installLog4JFormat(this, TERMINAL_NAME, configuration); 67 | } catch (ReflectiveOperationException reflectiveEx) { 68 | logger.error("Failed to install log format", reflectiveEx); 69 | } 70 | } 71 | 72 | @Override 73 | public void revertLogFormat() { 74 | try { 75 | installer.revertLog4JFormat(TERMINAL_NAME, oldLayout); 76 | } catch (ReflectiveOperationException reflectiveEx) { 77 | logger.warn("Cannot revert log format", reflectiveEx); 78 | } 79 | } 80 | 81 | @Override 82 | public ColorAppender createAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol) { 83 | return new SpongeAppender(oldAppender, hideMessages, truncateCol); 84 | } 85 | 86 | @Override 87 | public Path getPluginFolder() { 88 | return pluginFolder; 89 | } 90 | 91 | @Override 92 | public ConsoleConfig loadConfiguration() throws IOException { 93 | Path configPath = pluginFolder.resolve(CONFIG_NAME); 94 | YAMLConfigurationLoader configLoader = YAMLConfigurationLoader.builder().setPath(configPath).build(); 95 | 96 | ConsoleConfig consoleConfig = new ConsoleConfig(); 97 | ConfigurationNode rootNode = configLoader.load(); 98 | consoleConfig.setLogFormat(rootNode.getNode("logFormat").getString()); 99 | consoleConfig.setDateStyle(rootNode.getNode("dateStyle").getString()); 100 | 101 | consoleConfig.getLevelColors().clear(); 102 | if (rootNode.getNode("colorLoggingLevel").getBoolean()) { 103 | ConfigurationNode levelSection = rootNode.getNode("Level"); 104 | for (LoggingLevel level : LoggingLevel.values()) { 105 | consoleConfig.getLevelColors().put(level, levelSection.getNode(level.name()).getString("")); 106 | } 107 | } 108 | 109 | consoleConfig.getPluginColors().clear(); 110 | if (rootNode.getNode("colorPluginTag").getBoolean()) { 111 | ConfigurationNode pluginSection = rootNode.getNode("Plugin"); 112 | consoleConfig.setDefaultPluginColor(pluginSection.getNode(ConsoleConfig.DEFAULT_PLUGIN_KEY).getString("")); 113 | for (Entry pluginEntry : pluginSection.getChildrenMap().entrySet()) { 114 | consoleConfig.getPluginColors().put((String) pluginEntry.getKey(), pluginEntry.getValue().getString()); 115 | } 116 | } 117 | 118 | consoleConfig.getHideMessages().clear(); 119 | try { 120 | List list = rootNode.getNode("hide-messages").getList(TypeToken.of(String.class)); 121 | consoleConfig.getHideMessages().addAll(list); 122 | } catch (ObjectMappingException mappingException) { 123 | throw new IOException(mappingException); 124 | } 125 | 126 | consoleConfig.setTruncateColor(rootNode.getNode("truncateColor").getBoolean()); 127 | return consoleConfig; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/github/games647/colorconsole/sponge/SpongeAppender.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.sponge; 2 | 3 | import com.github.games647.colorconsole.common.ColorAppender; 4 | 5 | import java.util.Collection; 6 | 7 | import org.apache.logging.log4j.core.Appender; 8 | import org.spongepowered.api.Sponge; 9 | import org.spongepowered.api.plugin.PluginContainer; 10 | 11 | import static java.util.stream.Collectors.toSet; 12 | 13 | public class SpongeAppender extends ColorAppender { 14 | 15 | public SpongeAppender(Appender oldAppender, Collection hideMessages, boolean truncateCol) { 16 | super(oldAppender, hideMessages, truncateCol); 17 | } 18 | 19 | @Override 20 | protected Collection loadPluginNames() { 21 | return Sponge.getPluginManager().getPlugins().stream() 22 | .map(PluginContainer::getId) 23 | .collect(toSet()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | # project informations for BungeeCord 2 | # This file will be prioritised over plugin.yml which can be also used for Bungee 3 | # This make it easy to combine BungeeCord and Bukkit support in one plugin 4 | name: ${project.name} 5 | # ${-} will be automatically replaced by Maven 6 | main: ${project.groupId}.${project.artifactId}.bungee.${project.name}Bungee 7 | 8 | version: ${project.version} 9 | author: games647, https://github.com/games647/ColorConsole/graphs/contributors 10 | 11 | description: | 12 | ${project.description} 13 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # ConsoleColor config 2 | 3 | # How the messages should be displayed 4 | # 5 | # Variables: 6 | # %thread - Thread name 7 | # %d{HH:mm:ss} - Timestamp 8 | # %msg - log message 9 | # %n - new line 10 | # These variables try to get the origin. This is an expensive operation and may impact performance. Use with caution. 11 | # %class{precision} - Class name 12 | # %method - Method name 13 | # %line - Line number 14 | # 15 | # For more details visit: https://logging.apache.org/log4j/2.x/manual/layouts.html#Patterns 16 | logFormat: '[%d{HH:mm:ss} %level]: %msg%n' 17 | 18 | # How should the time be highlighted 19 | # Like below it could also be default which means it's the default font color depending on your terminal settings. 20 | dateStyle: cyan 21 | 22 | # Should the log message be highlighted depending on the logging level 23 | colorLoggingLevel: true 24 | 25 | # Log Level Colors 26 | Level: 27 | FATAL: red 28 | ERROR: red 29 | WARN: yellow 30 | INFO: green 31 | DEBUG: green 32 | TRACE: blue 33 | 34 | # Should the plugin tag [PLUGIN_NAME] be highlighted 35 | colorPluginTag: true 36 | 37 | # Plugin Colors 38 | 39 | # This can be the default color or "random" it gives each plugin (besides the ones specified below) a different color 40 | # which keeps the same until the server shuts down. 41 | # Black is ignored by default, because it's often hard to read on the console 42 | 43 | Plugin: 44 | Default: random 45 | Essentials: green 46 | LagMonitor: red 47 | WorldEdit: red 48 | FastLogin: cyan 49 | WorldGuard: cyan 50 | Vault: magenta 51 | ChangeSkin: yellow 52 | ScoreboardStats: white 53 | mcMMOAction: blue 54 | mcMMOExtras: yellow 55 | ColorConsole: orange 56 | 57 | # Available foreground colors | Available background colors 58 | # Black | BG_Black 59 | # Red | BG_Red 60 | # Green | BG_Green 61 | # Yellow | BG_Yellow 62 | # Blue | BG_Blue 63 | # Magenta | BG_Magenta 64 | # Cyan | BG_Cyan 65 | # White | BG_White 66 | # Default | 67 | 68 | # Available styling options 69 | # blink | Blinking characters 70 | # bold | Bold 71 | # underline | Underlined characters 72 | # reverse | Reverse video 73 | # dim | Dimmed or faint characters 74 | # italic | italic 75 | # hidden | 76 | 77 | # Hides the log message if it contains one or more of the following texts 78 | # The texts are case-sensitive 79 | hide-messages: 80 | - 'ThisIsATest' 81 | - 'SecondTest' 82 | 83 | # Removes color formatting if the complete message has color formatting 84 | truncateColor: false 85 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | # project data for Bukkit in order to register our plugin with all it components 2 | # ${-} are variables from Maven (pom.xml) which will be replaced after the build 3 | name: ${project.name} 4 | version: ${project.version} 5 | main: ${project.groupId}.${project.artifactId}.bukkit.${project.name}Bukkit 6 | 7 | # meta data for plugin managers 8 | authors: [games647, 'https://github.com/games647/ColorConsole/graphs/contributors'] 9 | description: | 10 | ${project.description} 11 | website: ${project.url} 12 | dev-url: ${project.url} 13 | 14 | # This plugin don't have to be transformed for compatibility with Minecraft >= 1.13 15 | api-version: 1.13 16 | -------------------------------------------------------------------------------- /src/test/java/com/github/games647/colorconsole/common/CommonFormatterTest.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.fusesource.jansi.Ansi; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | 14 | import static org.junit.Assert.assertThat; 15 | 16 | public class CommonFormatterTest { 17 | 18 | private CommonFormatter formatter; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | formatter = new CommonFormatter(Collections.singleton("ignore"), false); 23 | } 24 | 25 | @Test 26 | public void testShouldIgnore() { 27 | assertThat(formatter.shouldIgnore("123"), is(false)); 28 | 29 | assertThat(formatter.shouldIgnore("ignore"), is(true)); 30 | assertThat(formatter.shouldIgnore("start ignore end"), is(true)); 31 | } 32 | 33 | @Test 34 | public void testColorizePluginTagPresent() { 35 | loadPluginColors(); 36 | 37 | Ansi reset = Ansi.ansi().reset(); 38 | String expected = "[" + Ansi.ansi().fgBlue() + "TestPlugin" + reset + "] msg" + reset; 39 | assertThat(formatter.colorizePluginTag("[TestPlugin] msg"), is(expected)); 40 | } 41 | 42 | @Test 43 | public void testColorizePluginTagNotPresentRight() { 44 | loadPluginColors(); 45 | 46 | // unmodified 47 | String msg = "[TestPlugin msg"; 48 | assertThat(formatter.colorizePluginTag(msg), is(msg)); 49 | } 50 | 51 | @Test 52 | public void testColorizePluginTagNotPresentLeft() { 53 | loadPluginColors(); 54 | 55 | // unmodified 56 | String msg = "TestPlugin] msg"; 57 | assertThat(formatter.colorizePluginTag(msg), is(msg)); 58 | } 59 | 60 | @Test 61 | public void testColorizePluginTagWrongOrder() { 62 | loadPluginColors(); 63 | 64 | // unmodified 65 | String msg = "]TestPlugin[ msg"; 66 | assertThat(formatter.colorizePluginTag(msg), is(msg)); 67 | } 68 | 69 | @Test 70 | public void testColorizeNameDefault() { 71 | loadPluginColors(); 72 | 73 | Ansi reset = Ansi.ansi().reset(); 74 | assertThat(formatter.colorizePluginName("None"), is(Ansi.ansi().fgRed() + "None" + reset)); 75 | } 76 | 77 | @Test 78 | public void testColorizePluginName() { 79 | loadPluginColors(); 80 | 81 | Ansi reset = Ansi.ansi().reset(); 82 | assertThat(formatter.colorizePluginName("TestPlugin"), is(Ansi.ansi().fgBlue() + "TestPlugin" + reset)); 83 | } 84 | 85 | private void loadPluginColors() { 86 | List plugins = Arrays.asList("TestPlugin", "None"); 87 | Map map = Collections.singletonMap("TestPlugin", "blue"); 88 | formatter.initPluginColors(plugins, map, "red"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/github/games647/colorconsole/common/Log4JInstallerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.colorconsole.common; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.logging.log4j.core.layout.PatternLayout; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class Log4JInstallerTest { 15 | 16 | private Log4JInstaller installer; 17 | 18 | @Before 19 | public void setUp() throws Exception { 20 | this.installer = new Log4JInstaller(); 21 | } 22 | 23 | @Test 24 | public void testFormatDateVanilla() { 25 | String expected = "[%style{%d{HH:mm:ss}}{cyan} %level]: %msg%n"; 26 | 27 | String configFormat = "[%d{HH:mm:ss} %level]: %msg%n"; 28 | assertThat(installer.formatDate(configFormat, "cyan"), is(expected)); 29 | } 30 | 31 | @Test 32 | public void testMappingLevels() { 33 | Map levelColors = new HashMap<>(); 34 | levelColors.put(LoggingLevel.FATAL, "red"); 35 | levelColors.put(LoggingLevel.ERROR, "red"); 36 | levelColors.put(LoggingLevel.WARN, "yellow"); 37 | levelColors.put(LoggingLevel.INFO, "green"); 38 | levelColors.put(LoggingLevel.DEBUG, "green"); 39 | levelColors.put(LoggingLevel.TRACE, "blue"); 40 | 41 | String configFormat = "[%d{HH:mm:ss} %level]: %msg%n"; 42 | String expected = "[%d{HH:mm:ss} %highlight{%level}{FATAL=red, ERROR=red, WARN=yellow, " + 43 | "INFO=green, DEBUG=green, TRACE=blue}]: %msg%n"; 44 | assertThat(installer.mapLoggingLevels(configFormat, levelColors), is(expected)); 45 | } 46 | 47 | @Test 48 | public void testMinecrellDetection() { 49 | PatternLayout crellPattern = PatternLayout.newBuilder() 50 | .withPattern("%highlightError{[%d{HH:mm:ss} %level]: [%logger] %minecraftFormatting{%msg}%n%xEx}") 51 | .build(); 52 | String crellAppender = "net.minecrell.terminalconsole.TerminalConsoleAppender"; 53 | assertThat(installer.isMinecrellFormatted(crellPattern, ""), is(true)); 54 | assertThat(installer.isMinecrellFormatted(PatternLayout.createDefaultLayout(), crellAppender), is(true)); 55 | } 56 | 57 | @Test 58 | public void testVanillaDetection() { 59 | PatternLayout vanillaPattern = PatternLayout.newBuilder() 60 | .withPattern("[%d{HH:mm:ss}] [%t/%level]: %msg%n") 61 | .build(); 62 | String vanillaAppender = "org.apache.logging.log4j.core.appender.ConsoleAppender"; 63 | assertThat(installer.isMinecrellFormatted(vanillaPattern, "vanillaAppender"), is(false)); 64 | } 65 | } 66 | --------------------------------------------------------------------------------