/* * This entire file is sublicensed to you under GPLv3 or (at your option) any * later version. The original copyright notice is retained below. */ /* * Portions of this file are * Copyright (C) 2016-2017 Ronald Jack Jenkins Jr., SLF4Bukkit contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * Copyright (c) 2004-2012 QOS.ch * All rights reserved. * * 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 org.slf4j.impl; import info.ronjenkins.slf4bukkit.ColorMapper; import info.ronjenkins.slf4bukkit.ColorMapperFactory; import info.ronjenkins.slf4bukkit.ColorMarker; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.exception.ExceptionUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.Plugin; import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.event.Level; import org.slf4j.helpers.FormattingTuple; import org.slf4j.helpers.MessageFormatter; import org.yaml.snakeyaml.Yaml; import com.google.common.collect.ImmutableMap; /** *

* A merger of SLF4J's {@code SimpleLogger} and {@code JDK14LoggerAdapter}, * wired to log all messages to the enclosing Bukkit plugin. The plugin is * identified by reading the "name" attribute from {@code plugin.yml} in the * current classloader. *

* *

* Plugins that include SLF4Bukkit can use the following values in * {@code config.yml} to configure the behavior of SLF4Bukkit. SLF4Bukkit uses * Bukkit's plugin configuration API to retrieve config values, so both on-disk * and built-in {@code config.yml} behavior is supported. *

* * * *

* SLF4J messages at level {@code TRACE} or {@code DEBUG} are logged to Bukkit * at level {@code INFO} because Bukkit does not enable any levels higher than * {@code INFO}. Therefore, only SLF4J messages at level {@code TRACE} or * {@code DEBUG} show their SLF4J level in the message that is logged to the * server console. *

* *

* Because SLF4Bukkit's configuration comes from the plugin configuration, * SLF4Bukkit supports configuration reloading. To achieve this, call * {@link #init(boolean)} with argument {@code true} after calling * {@link Plugin#reloadConfig()}. *

* *

* It is possible for SLF4J loggers to be used before the plugin is registered * with Bukkit's plugin manager. SLF4Bukkit is considered to be * uninitialized as long as the plugin cannot be retrieved from Bukkit's * plugin manager. While in the uninitialized state, SLF4Bukkit: *

* * * *

* For this reason, it is strongly recommended that you not emit any log * messages via SLF4Bukkit until your plugin's {@link Plugin#onLoad() onLoad()} * method has begun execution. (You can safely log messages inside the * {@code onLoad()} method, because your plugin is registered by that time.) * Logging inside static initializers, the plugin class constructor and other * pre-plugin-registration areas of your code is discouraged. *

* *

* With no configuration, the default output includes the logger short name and * the message, followed by the line separator for the host. *

* *

* This logger supports only {@link ColorMarker}s, which are used to format the * logged message and throwable. All other marker types are ignored. The usage * of markers does not affect whether or not a given logging level is enabled. *

* *

* When executed on a Bukkit implementation that does not contain the JAnsi * library (e.g. PaperSpigot), all color-related functionality is silently * ignored. Any messages logged in such an environment by SLF4Bukkit will have * any {@link ChatColor} values stripped. SLF4Bukkit does not emit any warnings * when executed in an environment where JAnsi is not available. *

* * @author Ceki Gülcü * @author Scott Sanders * @author Rod Waldhoff * @author Robert Burrell Donkin * @author Cédrik LIME * @author Peter Royal * @author Ronald Jack Jenkins Jr. */ public final class BukkitLoggerAdapter implements Logger { // Plugin reference. private static transient Plugin BUKKIT_PLUGIN; private static transient String BUKKIT_PLUGIN_NAME; // Configuration parameters. private static final String CONFIG_FALLBACK_DEFAULT_LOG_LEVEL = "info"; private static final Map CONFIG_FALLBACK_LEVEL_COLORS = BukkitLoggerAdapter.fallbackLevelColors(); private static final boolean CONFIG_FALLBACK_SHOW_HEADER = false; private static final boolean CONFIG_FALLBACK_SHOW_LOG_NAME = false; private static final boolean CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME = true; private static final boolean CONFIG_FALLBACK_SHOW_THREAD_NAME = false; private static final String CONFIG_KEY_DEFAULT_LOG_LEVEL = "slf4j.defaultLogLevel"; private static final String CONFIG_KEY_LEVEL_COLORS = "slf4j.colors"; private static final String CONFIG_KEY_PREFIX_LOG = "slf4j.log."; private static final String CONFIG_KEY_SHOW_HEADER = "slf4j.showHeader"; private static final String CONFIG_KEY_SHOW_LOG_NAME = "slf4j.showLogName"; private static final String CONFIG_KEY_SHOW_SHORT_LOG_NAME = "slf4j.showShortLogName"; private static final String CONFIG_KEY_SHOW_THREAD_NAME = "slf4j.showThreadName"; private static Level CONFIG_VALUE_DEFAULT_LOG_LEVEL; private static Map CONFIG_VALUE_LEVEL_COLORS; private static boolean CONFIG_VALUE_SHOW_HEADER; private static boolean CONFIG_VALUE_SHOW_LOG_NAME; private static boolean CONFIG_VALUE_SHOW_SHORT_LOG_NAME; private static boolean CONFIG_VALUE_SHOW_THREAD_NAME; // Initialization lock. private static final Object INITIALIZATION_LOCK = new Object(); // The logger name. private final String name; // The short name of this simple log instance private final ColorMapper mapper = ColorMapperFactory.create(); private transient String shortLogName = null; // NOTE: BukkitPluginLoggerAdapter constructor should have only package access // so that only BukkitPluginLoggerFactory be able to create one. BukkitLoggerAdapter(final String name) { this.name = name; } /** * (Re)initializes all SLF4Bukkit loggers in this plugin, relying on the YAML * configuration of the plugin. * * @param reinitialize * set to {@code true} to reinitialize all loggers, e.g. after * reloading the plugin config. */ public static void init(final boolean reinitialize) { synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { // Do not re-initialize unless requested. if (reinitialize) { BukkitLoggerAdapter.BUKKIT_PLUGIN = null; BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME = null; } else if (BukkitLoggerAdapter.BUKKIT_PLUGIN != null) { return; } // Get a reference to the plugin in this classloader. if (BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME == null) { InputStream pluginYmlFile = null; try { pluginYmlFile = BukkitLoggerAdapter.class.getClassLoader() .getResource("plugin.yml") .openStream(); final Yaml yaml = new Yaml(); @SuppressWarnings("rawtypes") final Map pluginYml = (Map) yaml.load(pluginYmlFile); BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME = (String) pluginYml.get("name"); } catch (final IOException e) { throw new IllegalStateException(e); } finally { if (pluginYmlFile != null) { try { pluginYmlFile.close(); } catch (final IOException e) { e.printStackTrace(); } } } } // Try to get the plugin. The logging system will be considered // uninitialized until this becomes non-null. While it is null, the Bukkit // server logger will be used instead of the plugin logger, and all // default configuration options will be used. BukkitLoggerAdapter.BUKKIT_PLUGIN = Bukkit.getPluginManager() .getPlugin(BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME); // Get the configuration values. // 1. Look in the plugin's on-disk config. // 2. If the value is absent, use the plugin's built-in config. // 3. If the value is absent, use the default values hardcoded above. // (1 and 2 are handled by using the Bukkit API.) BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.getStringProperty(BukkitLoggerAdapter.CONFIG_KEY_DEFAULT_LOG_LEVEL, BukkitLoggerAdapter.CONFIG_FALLBACK_DEFAULT_LOG_LEVEL)); if (BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL == null) { BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.CONFIG_FALLBACK_DEFAULT_LOG_LEVEL); } BukkitLoggerAdapter.CONFIG_VALUE_LEVEL_COLORS = BukkitLoggerAdapter.getLevelColorsMap(BukkitLoggerAdapter.CONFIG_KEY_LEVEL_COLORS, BukkitLoggerAdapter.CONFIG_FALLBACK_LEVEL_COLORS); BukkitLoggerAdapter.CONFIG_VALUE_SHOW_HEADER = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_HEADER, BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_HEADER); BukkitLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_LOG_NAME, BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_LOG_NAME); BukkitLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_SHORT_LOG_NAME, BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME); BukkitLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_THREAD_NAME, BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_THREAD_NAME); } } /** * Returns the fallback map of logging levels to their default colors. * * @return never null. */ private static Map fallbackLevelColors() { return ImmutableMap. builder() .put(Level.ERROR, ColorMarker.RED) .put(Level.WARN, ColorMarker.YELLOW) .put(Level.INFO, ColorMarker.NONE) .put(Level.DEBUG, ColorMarker.NONE) .put(Level.TRACE, ColorMarker.NONE).build(); } /** * Returns a boolean property from the Bukkit plugin config. * * @param name * the desired property. * @param defaultValue * the fallback value returned by this method. * @return {@code defaultValue} if the Bukkit plugin is not available, if the * desired property is not defined in the config, or if the desired * property's value is not either "true" or "false" * (case-insensitive). */ private static boolean getBooleanProperty(final String name, final boolean defaultValue) { synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; } final String prop = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig() .getString(name); if ("true".equalsIgnoreCase(prop)) { return true; } if ("false".equalsIgnoreCase(prop)) { return false; } return defaultValue; } } /** * Returns the most appropriate logger. * * @return the logger for the plugin if available; otherwise the server * logger. Never null. */ private static java.util.logging.Logger getBukkitLogger() { synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { return BukkitLoggerAdapter.BUKKIT_PLUGIN == null ? Bukkit.getLogger() : BukkitLoggerAdapter.BUKKIT_PLUGIN.getLogger(); } } /** * Returns the map of logging levels to colors, taken from the Bukkit plugin * config. For each relevant entry in the plugin config, if either the key * name or the value name is invalid, that entry is ignored and the default * value is used instead. * * @param property * the config property where the map exists. * @param defaultValue * the fallback values returned by this method. * @return never null, always contains one mapping for each {@link Level}, and * contains no null keys/values. Equal to {@code defaultValue} if the * Bukkit plugin is not available, or if the desired property is not * defined in the config. */ private static Map getLevelColorsMap(final String property, final Map defaultValues) { synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { // Check for the plugin. if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValues; } final ConfigurationSection config = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig() .getConfigurationSection(property); // Quit if the config isn't specified. if (config == null) { return defaultValues; } // Translate each portion of the config. Skip invalid keys/values. final Map configValues = config.getValues(false); final Map convertedConfigValues = new HashMap(); for (final Map.Entry configValue : configValues.entrySet()) { final String levelName = configValue.getKey().toUpperCase(); final String formatName = configValue.getValue().toString() .toUpperCase(); Level level; ColorMarker format; try { level = Level.valueOf(levelName); format = ColorMarker.valueOf(formatName); } catch (final IllegalArgumentException e) { // This is expected, so don't log it. continue; } convertedConfigValues.put(level, format); } // Merge the default and config-based map; the latter takes priority. final Map finalConfigValues = new HashMap(); finalConfigValues.putAll(defaultValues); finalConfigValues.putAll(convertedConfigValues); // Done; cast as immutable. return ImmutableMap. builder() .putAll(finalConfigValues).build(); } } /** * Returns a string property from the Bukkit plugin config. * * @param name * the desired property. * @param defaultValue * the fallback value returned by this method. * @return {@code defaultValue} if the Bukkit plugin is not available, or if * the desired property is not defined in the config. */ private static String getStringProperty(final String name, final String defaultValue) { synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; } final String prop = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig() .getString(name); return (prop == null) ? defaultValue : prop; } } /** * Converts an SLF4J logging level to a Bukkit logging level. * *
    *
  • {@link Level#ERROR} maps to {@link java.util.logging.Level#SEVERE}.
  • *
  • {@link Level#WARN} maps to {@link java.util.logging.Level#WARNING}.
  • *
  • All others map to {@link java.util.logging.Level#INFO} (Bukkit won't * log any messages higher than {@code INFO}).
  • *
* * @param slf4jLevel * any SLF4J logging level. * @return never null. */ private static java.util.logging.Level slf4jLevelIntToBukkitJULLevel(final Level slf4jLevel) { java.util.logging.Level julLevel; switch (slf4jLevel) { case ERROR: julLevel = java.util.logging.Level.SEVERE; break; case WARN: julLevel = java.util.logging.Level.WARNING; break; default: // In Bukkit, Only the SEVERE, WARNING and INFO JUL levels are enabled, // so SLF4J's TRACE and DEBUG levels must be logged at Bukkit's INFO // level. julLevel = java.util.logging.Level.INFO; break; } return julLevel; } /** * Convert YAML logging level properties to SLF4J level objects. * * @param levelStr * the level property value from the YAML config. * @return null iff the input does not map to a SLF4J logging level name in a * case-insensitive fashion. */ private static Level stringToLevel(final String levelStr) { if ("trace".equalsIgnoreCase(levelStr)) { return Level.TRACE; } else if ("debug".equalsIgnoreCase(levelStr)) { return Level.DEBUG; } else if ("info".equalsIgnoreCase(levelStr)) { return Level.INFO; } else if ("warn".equalsIgnoreCase(levelStr)) { return Level.WARN; } else if ("error".equalsIgnoreCase(levelStr)) { return Level.ERROR; } else { return null; } } @Override public void debug(final Marker marker, final String msg) { if (!this.isDebugEnabled()) { return; } this.log(Level.DEBUG, marker, msg, null); } @Override public void debug(final Marker marker, final String format, final Object arg) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, marker, format, arg, null); } @Override public void debug(final Marker marker, final String format, final Object... arguments) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, marker, format, arguments); } @Override public void debug(final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, marker, format, arg1, arg2); } @Override public void debug(final Marker marker, final String msg, final Throwable t) { if (!this.isDebugEnabled()) { return; } this.log(Level.DEBUG, marker, msg, t); } @Override public void debug(final String msg) { if (!this.isDebugEnabled()) { return; } this.log(Level.DEBUG, null, msg, null); } @Override public void debug(final String format, final Object arg) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, null, format, arg, null); } @Override public void debug(final String format, final Object... arguments) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, null, format, arguments); } @Override public void debug(final String format, final Object arg1, final Object arg2) { if (!this.isDebugEnabled()) { return; } this.formatAndLog(Level.DEBUG, null, format, arg1, arg2); } @Override public void debug(final String msg, final Throwable t) { if (!this.isDebugEnabled()) { return; } this.log(Level.DEBUG, null, msg, t); } @Override public void error(final Marker marker, final String msg) { if (!this.isErrorEnabled()) { return; } this.log(Level.ERROR, marker, msg, null); } @Override public void error(final Marker marker, final String format, final Object arg) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, marker, format, arg, null); } @Override public void error(final Marker marker, final String format, final Object... arguments) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, marker, format, arguments); } @Override public void error(final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, marker, format, arg1, arg2); } @Override public void error(final Marker marker, final String msg, final Throwable t) { if (!this.isErrorEnabled()) { return; } this.log(Level.ERROR, marker, msg, t); } @Override public void error(final String msg) { if (!this.isErrorEnabled()) { return; } this.log(Level.ERROR, null, msg, null); } @Override public void error(final String format, final Object arg) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, null, format, arg, null); } @Override public void error(final String format, final Object... arguments) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, null, format, arguments); } @Override public void error(final String format, final Object arg1, final Object arg2) { if (!this.isErrorEnabled()) { return; } this.formatAndLog(Level.ERROR, null, format, arg1, arg2); } @Override public void error(final String msg, final Throwable t) { if (!this.isErrorEnabled()) { return; } this.log(Level.ERROR, null, msg, t); } @Override public String getName() { return this.name; } @Override public void info(final Marker marker, final String msg) { if (!this.isInfoEnabled()) { return; } this.log(Level.INFO, marker, msg, null); } @Override public void info(final Marker marker, final String format, final Object arg) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, marker, format, arg, null); } @Override public void info(final Marker marker, final String format, final Object... arguments) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, marker, format, arguments); } @Override public void info(final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, marker, format, arg1, arg2); } @Override public void info(final Marker marker, final String msg, final Throwable t) { if (!this.isInfoEnabled()) { return; } this.log(Level.INFO, marker, msg, t); } @Override public void info(final String msg) { if (!this.isInfoEnabled()) { return; } this.log(Level.INFO, null, msg, null); } @Override public void info(final String format, final Object arg) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, null, format, arg, null); } @Override public void info(final String format, final Object... arguments) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, null, format, arguments); } @Override public void info(final String format, final Object arg1, final Object arg2) { if (!this.isInfoEnabled()) { return; } this.formatAndLog(Level.INFO, null, format, arg1, arg2); } @Override public void info(final String msg, final Throwable t) { if (!this.isInfoEnabled()) { return; } this.log(Level.INFO, null, msg, t); } @Override public boolean isDebugEnabled() { return this.isLevelEnabled(Level.DEBUG); } @Override public boolean isDebugEnabled(final Marker marker) { return this.isLevelEnabled(Level.DEBUG); } @Override public boolean isErrorEnabled() { return this.isLevelEnabled(Level.ERROR); } @Override public boolean isErrorEnabled(final Marker marker) { return this.isLevelEnabled(Level.ERROR); } @Override public boolean isInfoEnabled() { return this.isLevelEnabled(Level.INFO); } @Override public boolean isInfoEnabled(final Marker marker) { return this.isLevelEnabled(Level.INFO); } @Override public boolean isTraceEnabled() { return this.isLevelEnabled(Level.TRACE); } @Override public boolean isTraceEnabled(final Marker marker) { return this.isLevelEnabled(Level.TRACE); } @Override public boolean isWarnEnabled() { return this.isLevelEnabled(Level.WARN); } @Override public boolean isWarnEnabled(final Marker marker) { return this.isLevelEnabled(Level.WARN); } @Override public void trace(final Marker marker, final String msg) { if (!this.isTraceEnabled()) { return; } this.log(Level.TRACE, marker, msg, null); } @Override public void trace(final Marker marker, final String format, final Object arg) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, marker, format, arg, null); } @Override public void trace(final Marker marker, final String format, final Object... arguments) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, marker, format, arguments); } @Override public void trace(final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, marker, format, arg1, arg2); } @Override public void trace(final Marker marker, final String msg, final Throwable t) { if (!this.isTraceEnabled()) { return; } this.log(Level.TRACE, marker, msg, t); } @Override public void trace(final String msg) { if (!this.isTraceEnabled()) { return; } this.log(Level.TRACE, null, msg, null); } @Override public void trace(final String format, final Object arg) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, null, format, arg, null); } @Override public void trace(final String format, final Object... arguments) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, null, format, arguments); } @Override public void trace(final String format, final Object arg1, final Object arg2) { if (!this.isTraceEnabled()) { return; } this.formatAndLog(Level.TRACE, null, format, arg1, arg2); } @Override public void trace(final String msg, final Throwable t) { if (!this.isTraceEnabled()) { return; } this.log(Level.TRACE, null, msg, t); } @Override public void warn(final Marker marker, final String msg) { if (!this.isWarnEnabled()) { return; } this.log(Level.WARN, marker, msg, null); } @Override public void warn(final Marker marker, final String format, final Object arg) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, marker, format, arg, null); } @Override public void warn(final Marker marker, final String format, final Object... arguments) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, marker, format, arguments); } @Override public void warn(final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, marker, format, arg1, arg2); } @Override public void warn(final Marker marker, final String msg, final Throwable t) { if (!this.isWarnEnabled()) { return; } this.log(Level.WARN, marker, msg, t); } @Override public void warn(final String msg) { if (!this.isWarnEnabled()) { return; } this.log(Level.WARN, null, msg, null); } @Override public void warn(final String format, final Object arg) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, null, format, arg, null); } @Override public void warn(final String format, final Object... arguments) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, null, format, arguments); } @Override public void warn(final String format, final Object arg1, final Object arg2) { if (!this.isWarnEnabled()) { return; } this.formatAndLog(Level.WARN, null, format, arg1, arg2); } @Override public void warn(final String msg, final Throwable t) { if (!this.isWarnEnabled()) { return; } this.log(Level.WARN, null, msg, t); } /** * Computes this logger's short name, which is equivalent to the short Java * package name format (e.g. a logger named "info.ronjenkins.bukkit.MyPlugin" * would have a short name of "i.r.b.MyPlugin"). * * @return never null. */ private String computeShortName() { final List splitName = new ArrayList(); splitName.addAll(Arrays.asList(this.name.split("\\."))); final int shortNameLength = ((splitName.size() - 1) * 2) + splitName.get(splitName.size() - 1).length(); final String finalName = splitName.remove(splitName.size() - 1); final StringBuffer shortName = new StringBuffer(shortNameLength); for (final String part : splitName) { shortName.append(part.charAt(0)).append('.'); } shortName.append(finalName); return shortName.toString(); } /** * Computes this logger's current logging level, based on the Bukkit plugin * config. * * @return the value of "slf4j.defaultLogLevel" if neither this logger nor any * of its ancestors define a logging level. */ private Level determineCurrentLevel() { // Compute the current level, which may be null. String tempName = this.name; Level level = null; int indexOfLastDot = tempName.length(); while ((level == null) && (indexOfLastDot > -1)) { tempName = tempName.substring(0, indexOfLastDot); level = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.getStringProperty(BukkitLoggerAdapter.CONFIG_KEY_PREFIX_LOG + tempName, null)); indexOfLastDot = String.valueOf(tempName).lastIndexOf("."); } // Return the default value if we got null. return (level == null) ? BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL : level; } /** * For formatted messages, first substitute arguments and then log. * * @param level * the level of this message. * @param marker * the marker to use for this message, may be null. * @param format * the message format string. * @param arguments * 3 or more arguments. */ private void formatAndLog(final Level level, final Marker marker, final String format, final Object... arguments) { if (!this.isLevelEnabled(level)) { return; } final FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments); this.log(level, marker, tp.getMessage(), tp.getThrowable()); } /** * For formatted messages, first substitute arguments and then log. * * @param level * the level of this message. * @param marker * the marker to use for this message, may be null. * @param format * the message format string. * @param arg1 * format argument #1. * @param arg2 * format argument #2. */ private void formatAndLog(final Level level, final Marker marker, final String format, final Object arg1, final Object arg2) { if (!this.isLevelEnabled(level)) { return; } final FormattingTuple tp = MessageFormatter.format(format, arg1, arg2); this.log(level, marker, tp.getMessage(), tp.getThrowable()); } /** * Is the given log level currently enabled? * * @param logLevel * is this level enabled? * @return true if enabled, false if disabled. */ private boolean isLevelEnabled(final Level logLevel) { // Ensure that SLF4Bukkit is initialized. Every public API call passes // through this method, so this is the appropriate place to ensure // initialization. BukkitLoggerAdapter.init(false); // log level are numerically ordered so can use simple numeric comparison // // the PLUGIN.getLogger().isLoggable() check avoids the unconditional // construction of location data for disabled log statements. As of // 2008-07-31, callers of this method do not perform this check. See also // http://jira.qos.ch/browse/SLF4J-81 final Level currentLogLevel = this.determineCurrentLevel(); return (logLevel.toInt() >= currentLogLevel.toInt()) && (BukkitLoggerAdapter.getBukkitLogger().isLoggable(BukkitLoggerAdapter.slf4jLevelIntToBukkitJULLevel(logLevel))); } /** * Assembles the final log message and sends it to the appropriate Bukkit * logger. * * @param level * the desired log level of the message. * @param marker * the marker to use for this message, may be null. * @param message * the message to be logged. * @param throwable * the exception to be logged, may be null. */ private void log(final Level level, final Marker marker, final String message, final Throwable throwable) { final java.util.logging.Logger logger; synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) { // Ensure that the logger will accept this request. if (!this.isLevelEnabled(level)) { return; } // Determine which logger will be used. logger = BukkitLoggerAdapter.getBukkitLogger(); } // Start building the log message. final StringBuilder buf = new StringBuilder(32); boolean hasHeader = false; // Use the marker, if applicable. Otherwise, use the default color for // this level. if (marker instanceof ColorMarker) { buf.append(((ColorMarker) marker).getValue()); } else { buf.append(BukkitLoggerAdapter.CONFIG_VALUE_LEVEL_COLORS.get(level) .getValue()); } // Indicate that this message comes from SLF4J, if desired. if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_HEADER) { hasHeader = true; buf.append("[SLF4J]"); } // Print a readable representation of the log level, but only for log levels // that Bukkit would otherwise eat. switch (level) { case TRACE: hasHeader = true; buf.append("[TRACE]"); break; case DEBUG: hasHeader = true; buf.append("[DEBUG]"); break; default: break; } // Append the current thread name, if desired. if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME) { hasHeader = true; buf.append('['); buf.append(Thread.currentThread().getName()); buf.append("]"); } // Buffer the current output with a space, unless there is no output. if (hasHeader) { buf.append(' '); } // Append the name of the log instance, if desired. if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME) { buf.append('{').append(this.name).append("} "); } else if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME) { if (this.shortLogName == null) { this.shortLogName = this.computeShortName(); } buf.append('{').append(this.shortLogName).append("} "); } // Append the message. buf.append(message); // Append the throwable, if applicable. if (throwable != null) { buf.append('\n').append(ExceptionUtils.getFullStackTrace(throwable) .trim()); } // Append a reset directive. buf.append(ChatColor.RESET); // Log the message. logger.log(BukkitLoggerAdapter.slf4jLevelIntToBukkitJULLevel(level), mapper.map(buf.toString())); } }