View Javadoc
1   /*
2    * This entire file is sublicensed to you under GPLv3 or (at your option) any
3    * later version. The original copyright notice is retained below.
4    */
5   /*
6    * Portions of this file are
7    * Copyright (C) 2016-2017 Ronald Jack Jenkins Jr., SLF4Bukkit contributors.
8    *
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation, either version 3 of the License, or
12   * (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21   */
22  /**
23   * Copyright (c) 2004-2012 QOS.ch
24   * All rights reserved.
25   *
26   * Permission is hereby granted, free  of charge, to any person obtaining
27   * a  copy  of this  software  and  associated  documentation files  (the
28   * "Software"), to  deal in  the Software without  restriction, including
29   * without limitation  the rights to  use, copy, modify,  merge, publish,
30   * distribute,  sublicense, and/or sell  copies of  the Software,  and to
31   * permit persons to whom the Software  is furnished to do so, subject to
32   * the following conditions:
33   *
34   * The  above  copyright  notice  and  this permission  notice  shall  be
35   * included in all copies or substantial portions of the Software.
36   *
37   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
38   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
39   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
40   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
41   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
42   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
43   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44   */
45  package org.slf4j.impl;
46  
47  import info.ronjenkins.slf4bukkit.ColorMapper;
48  import info.ronjenkins.slf4bukkit.ColorMapperFactory;
49  import info.ronjenkins.slf4bukkit.ColorMarker;
50  
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.HashMap;
56  import java.util.List;
57  import java.util.Map;
58  
59  import org.apache.commons.lang.exception.ExceptionUtils;
60  import org.bukkit.Bukkit;
61  import org.bukkit.ChatColor;
62  import org.bukkit.configuration.ConfigurationSection;
63  import org.bukkit.plugin.Plugin;
64  import org.slf4j.Logger;
65  import org.slf4j.Marker;
66  import org.slf4j.event.Level;
67  import org.slf4j.helpers.FormattingTuple;
68  import org.slf4j.helpers.MessageFormatter;
69  import org.yaml.snakeyaml.Yaml;
70  
71  import com.google.common.collect.ImmutableMap;
72  
73  /**
74   * <p>
75   * A merger of SLF4J's {@code SimpleLogger} and {@code JDK14LoggerAdapter},
76   * wired to log all messages to the enclosing Bukkit plugin. The plugin is
77   * identified by reading the "name" attribute from {@code plugin.yml} in the
78   * current classloader.
79   * </p>
80   *
81   * <p>
82   * Plugins that include SLF4Bukkit can use the following values in
83   * {@code config.yml} to configure the behavior of SLF4Bukkit. SLF4Bukkit uses
84   * Bukkit's plugin configuration API to retrieve config values, so both on-disk
85   * and built-in {@code config.yml} behavior is supported.
86   * </p>
87   *
88   * <ul>
89   * <li>{@code slf4j.defaultLogLevel} - Default log level for all SLF4Bukkit
90   * loggers in this plugin. Must be one of "trace", "debug", "info", "warn", or
91   * "error" (case-insensitive). If unspecified or given any other value, defaults
92   * to "info".</li>
93   *
94   * <li>{@code slf4j.showHeader} -Set to {@code true} if you want to output the
95   * {@code [SLF4J]} header. If unspecified or given any other value, defaults to
96   * {@code false}.</li>
97   *
98   * <li>{@code slf4j.showLogName} - Set to {@code true} if you want the logger
99   * instance name (wrapped in curly braces) to be included in output messages. If
100  * unspecified or given any other value, defaults to {@code false}. If this
101  * option is {@code true}, it overrides {@code slf4j.showShortLogName}.</li>
102  *
103  * <li>{@code slf4j.showShortLogName} - Set to {@code true} if you want the
104  * logger instance's short name (wrapped in curly braces) to be included in
105  * output messages. The short name is equal to the full name with every
106  * dot-separated portion of the full name (except the last portion) truncated to
107  * its first character. If unspecified or given any other value, defaults to
108  * {@code true}. This option is ignored if {@code slf4j.showLogName} is
109  * {@code true}.</li>
110  *
111  * <li>{@code slf4j.showThreadName} -Set to {@code true} if you want to output
112  * the current thread name, wrapped in brackets. If unspecified or given any
113  * other value, defaults to {@code false}.</li>
114  *
115  * <li>{@code slf4j.colors.LEVEL} - Default color for all messages of this
116  * level. Possible values come from SLF4Bukkit's {@link ColorMarker} values.
117  * Both keys and values in this section are treated as case-insensitive. Invalid
118  * values for either the key or value of an entry result in that entry being
119  * ignored. Default values are: error=RED, warn=YELLOW, others=NONE. When used
120  * programmatically via methods in this class, {@link ColorMarker}s always
121  * override these config values.</li>
122  *
123  * <li>{@code slf4j.log.<em>a.b.c</em>} - Logging detail level for an SLF4Bukkit
124  * logger instance in this plugin named "a.b.c". Right-side value must be one of
125  * "trace", "debug", "info", "warn", or "error" (case-insensitive). When a
126  * logger named "a.b.c" is initialized, its level is assigned from this
127  * property. If unspecified or given any other value, the level of the nearest
128  * parent logger will be used. If no parent logger level is set, then the value
129  * specified by {@code slf4j.defaultLogLevel} for this plugin will be used.</li>
130  * </ul>
131  *
132  * <p>
133  * SLF4J messages at level {@code TRACE} or {@code DEBUG} are logged to Bukkit
134  * at level {@code INFO} because Bukkit does not enable any levels higher than
135  * {@code INFO}. Therefore, only SLF4J messages at level {@code TRACE} or
136  * {@code DEBUG} show their SLF4J level in the message that is logged to the
137  * server console.
138  * </p>
139  *
140  * <p>
141  * Because SLF4Bukkit's configuration comes from the plugin configuration,
142  * SLF4Bukkit supports configuration reloading. To achieve this, call
143  * {@link #init(boolean)} with argument {@code true} after calling
144  * {@link Plugin#reloadConfig()}.
145  * </p>
146  *
147  * <p>
148  * It is possible for SLF4J loggers to be used before the plugin is registered
149  * with Bukkit's plugin manager. SLF4Bukkit is considered to be
150  * <i>uninitialized</i> as long as the plugin cannot be retrieved from Bukkit's
151  * plugin manager. While in the uninitialized state, SLF4Bukkit:
152  * </p>
153  *
154  * <ul>
155  * <li>uses {@link Bukkit#getLogger()} instead of {@link Plugin#getLogger()}.</li>
156  * <li>uses the default configuration values (see above).</li>
157  * <li>attempts to initialize itself upon every logging call until the plugin is
158  * retrievable from Bukkit's plugin manager, at which point SLF4Bukkit is
159  * considered to be <i>initialized</i>. Once initialized,
160  * {@link Plugin#getLogger()} and {@link Plugin#getConfig() the plugin YAML
161  * configuration values} are used.</li>
162  * </ul>
163  *
164  * <p>
165  * For this reason, it is strongly recommended that you not emit any log
166  * messages via SLF4Bukkit until your plugin's {@link Plugin#onLoad() onLoad()}
167  * method has begun execution. (You can safely log messages inside the
168  * {@code onLoad()} method, because your plugin is registered by that time.)
169  * Logging inside static initializers, the plugin class constructor and other
170  * pre-plugin-registration areas of your code is discouraged.
171  * </p>
172  *
173  * <p>
174  * With no configuration, the default output includes the logger short name and
175  * the message, followed by the line separator for the host.
176  * </p>
177  *
178  * <p>
179  * This logger supports only {@link ColorMarker}s, which are used to format the
180  * logged message and throwable. All other marker types are ignored. The usage
181  * of markers does not affect whether or not a given logging level is enabled.
182  * </p>
183  *
184  * <p>
185  * When executed on a Bukkit implementation that does not contain the JAnsi
186  * library (e.g. PaperSpigot), all color-related functionality is silently
187  * ignored. Any messages logged in such an environment by SLF4Bukkit will have
188  * any {@link ChatColor} values stripped. SLF4Bukkit does not emit any warnings
189  * when executed in an environment where JAnsi is not available.
190  * </p>
191  *
192  * @author Ceki G&uuml;lc&uuml;
193  * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
194  * @author Rod Waldhoff
195  * @author Robert Burrell Donkin
196  * @author C&eacute;drik LIME
197  * @author Peter Royal
198  * @author Ronald Jack Jenkins Jr.
199  */
200 public final class BukkitLoggerAdapter implements Logger {
201 
202   // Plugin reference.
203   private static transient Plugin              BUKKIT_PLUGIN;
204   private static transient String              BUKKIT_PLUGIN_NAME;
205   // Configuration parameters.
206   private static final String                  CONFIG_FALLBACK_DEFAULT_LOG_LEVEL   = "info";
207   private static final Map<Level, ColorMarker> CONFIG_FALLBACK_LEVEL_COLORS        = BukkitLoggerAdapter.fallbackLevelColors();
208   private static final boolean                 CONFIG_FALLBACK_SHOW_HEADER         = false;
209   private static final boolean                 CONFIG_FALLBACK_SHOW_LOG_NAME       = false;
210   private static final boolean                 CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME = true;
211   private static final boolean                 CONFIG_FALLBACK_SHOW_THREAD_NAME    = false;
212   private static final String                  CONFIG_KEY_DEFAULT_LOG_LEVEL        = "slf4j.defaultLogLevel";
213   private static final String                  CONFIG_KEY_LEVEL_COLORS             = "slf4j.colors";
214   private static final String                  CONFIG_KEY_PREFIX_LOG               = "slf4j.log.";
215   private static final String                  CONFIG_KEY_SHOW_HEADER              = "slf4j.showHeader";
216   private static final String                  CONFIG_KEY_SHOW_LOG_NAME            = "slf4j.showLogName";
217   private static final String                  CONFIG_KEY_SHOW_SHORT_LOG_NAME      = "slf4j.showShortLogName";
218   private static final String                  CONFIG_KEY_SHOW_THREAD_NAME         = "slf4j.showThreadName";
219   private static Level                         CONFIG_VALUE_DEFAULT_LOG_LEVEL;
220   private static Map<Level, ColorMarker>       CONFIG_VALUE_LEVEL_COLORS;
221   private static boolean                       CONFIG_VALUE_SHOW_HEADER;
222   private static boolean                       CONFIG_VALUE_SHOW_LOG_NAME;
223   private static boolean                       CONFIG_VALUE_SHOW_SHORT_LOG_NAME;
224   private static boolean                       CONFIG_VALUE_SHOW_THREAD_NAME;
225   // Initialization lock.
226   private static final Object                  INITIALIZATION_LOCK                 = new Object();
227   // The logger name.
228   private final String                         name;
229   // The short name of this simple log instance
230   private final ColorMapper                    mapper                              = ColorMapperFactory.create();
231   private transient String                     shortLogName                        = null;
232 
233   // NOTE: BukkitPluginLoggerAdapter constructor should have only package access
234   // so that only BukkitPluginLoggerFactory be able to create one.
235   BukkitLoggerAdapter(final String name) {
236     this.name = name;
237   }
238 
239   /**
240    * (Re)initializes all SLF4Bukkit loggers in this plugin, relying on the YAML
241    * configuration of the plugin.
242    *
243    * @param reinitialize
244    *          set to {@code true} to reinitialize all loggers, e.g. after
245    *          reloading the plugin config.
246    */
247   public static void init(final boolean reinitialize) {
248     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
249       // Do not re-initialize unless requested.
250       if (reinitialize) {
251         BukkitLoggerAdapter.BUKKIT_PLUGIN = null;
252         BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME = null;
253       } else if (BukkitLoggerAdapter.BUKKIT_PLUGIN != null) { return; }
254       // Get a reference to the plugin in this classloader.
255       if (BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME == null) {
256         InputStream pluginYmlFile = null;
257         try {
258           pluginYmlFile = BukkitLoggerAdapter.class.getClassLoader()
259                                                    .getResource("plugin.yml")
260                                                    .openStream();
261           final Yaml yaml = new Yaml();
262           @SuppressWarnings("rawtypes")
263           final Map pluginYml = (Map) yaml.load(pluginYmlFile);
264           BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME = (String) pluginYml.get("name");
265         } catch (final IOException e) {
266           throw new IllegalStateException(e);
267         } finally {
268           if (pluginYmlFile != null) {
269             try {
270               pluginYmlFile.close();
271             } catch (final IOException e) {
272               e.printStackTrace();
273             }
274           }
275         }
276       }
277       // Try to get the plugin. The logging system will be considered
278       // uninitialized until this becomes non-null. While it is null, the Bukkit
279       // server logger will be used instead of the plugin logger, and all
280       // default configuration options will be used.
281       BukkitLoggerAdapter.BUKKIT_PLUGIN = Bukkit.getPluginManager()
282                                                 .getPlugin(BukkitLoggerAdapter.BUKKIT_PLUGIN_NAME);
283       // Get the configuration values.
284       // 1. Look in the plugin's on-disk config.
285       // 2. If the value is absent, use the plugin's built-in config.
286       // 3. If the value is absent, use the default values hardcoded above.
287       // (1 and 2 are handled by using the Bukkit API.)
288       BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.getStringProperty(BukkitLoggerAdapter.CONFIG_KEY_DEFAULT_LOG_LEVEL,
289                                                                                                                                    BukkitLoggerAdapter.CONFIG_FALLBACK_DEFAULT_LOG_LEVEL));
290       if (BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL == null) {
291         BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.CONFIG_FALLBACK_DEFAULT_LOG_LEVEL);
292       }
293       BukkitLoggerAdapter.CONFIG_VALUE_LEVEL_COLORS = BukkitLoggerAdapter.getLevelColorsMap(BukkitLoggerAdapter.CONFIG_KEY_LEVEL_COLORS,
294                                                                                             BukkitLoggerAdapter.CONFIG_FALLBACK_LEVEL_COLORS);
295       BukkitLoggerAdapter.CONFIG_VALUE_SHOW_HEADER = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_HEADER,
296                                                                                             BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_HEADER);
297       BukkitLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_LOG_NAME,
298                                                                                               BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_LOG_NAME);
299       BukkitLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_SHORT_LOG_NAME,
300                                                                                                     BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME);
301       BukkitLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME = BukkitLoggerAdapter.getBooleanProperty(BukkitLoggerAdapter.CONFIG_KEY_SHOW_THREAD_NAME,
302                                                                                                  BukkitLoggerAdapter.CONFIG_FALLBACK_SHOW_THREAD_NAME);
303     }
304   }
305 
306   /**
307    * Returns the fallback map of logging levels to their default colors.
308    *
309    * @return never null.
310    */
311   private static Map<Level, ColorMarker> fallbackLevelColors() {
312     return ImmutableMap.<Level, ColorMarker> builder()
313                        .put(Level.ERROR, ColorMarker.RED)
314                        .put(Level.WARN, ColorMarker.YELLOW)
315                        .put(Level.INFO, ColorMarker.NONE)
316                        .put(Level.DEBUG, ColorMarker.NONE)
317                        .put(Level.TRACE, ColorMarker.NONE).build();
318   }
319 
320   /**
321    * Returns a boolean property from the Bukkit plugin config.
322    *
323    * @param name
324    *          the desired property.
325    * @param defaultValue
326    *          the fallback value returned by this method.
327    * @return {@code defaultValue} if the Bukkit plugin is not available, if the
328    *         desired property is not defined in the config, or if the desired
329    *         property's value is not either "true" or "false"
330    *         (case-insensitive).
331    */
332   private static boolean getBooleanProperty(final String name,
333                                             final boolean defaultValue) {
334     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
335       if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; }
336       final String prop = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig()
337                                                            .getString(name);
338       if ("true".equalsIgnoreCase(prop)) { return true; }
339       if ("false".equalsIgnoreCase(prop)) { return false; }
340       return defaultValue;
341     }
342   }
343 
344   /**
345    * Returns the most appropriate logger.
346    *
347    * @return the logger for the plugin if available; otherwise the server
348    *         logger. Never null.
349    */
350   private static java.util.logging.Logger getBukkitLogger() {
351     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
352       return BukkitLoggerAdapter.BUKKIT_PLUGIN == null ? Bukkit.getLogger()
353                                                       : BukkitLoggerAdapter.BUKKIT_PLUGIN.getLogger();
354     }
355   }
356 
357   /**
358    * Returns the map of logging levels to colors, taken from the Bukkit plugin
359    * config. For each relevant entry in the plugin config, if either the key
360    * name or the value name is invalid, that entry is ignored and the default
361    * value is used instead.
362    *
363    * @param property
364    *          the config property where the map exists.
365    * @param defaultValue
366    *          the fallback values returned by this method.
367    * @return never null, always contains one mapping for each {@link Level}, and
368    *         contains no null keys/values. Equal to {@code defaultValue} if the
369    *         Bukkit plugin is not available, or if the desired property is not
370    *         defined in the config.
371    */
372   private static Map<Level, ColorMarker>
373       getLevelColorsMap(final String property,
374                         final Map<Level, ColorMarker> defaultValues) {
375     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
376       // Check for the plugin.
377       if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValues; }
378       final ConfigurationSection config = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig()
379                                                                            .getConfigurationSection(property);
380       // Quit if the config isn't specified.
381       if (config == null) { return defaultValues; }
382       // Translate each portion of the config. Skip invalid keys/values.
383       final Map<String, Object> configValues = config.getValues(false);
384       final Map<Level, ColorMarker> convertedConfigValues = new HashMap<Level, ColorMarker>();
385       for (final Map.Entry<String, Object> configValue : configValues.entrySet()) {
386         final String levelName = configValue.getKey().toUpperCase();
387         final String formatName = configValue.getValue().toString()
388                                              .toUpperCase();
389         Level level;
390         ColorMarker format;
391         try {
392           level = Level.valueOf(levelName);
393           format = ColorMarker.valueOf(formatName);
394         } catch (final IllegalArgumentException e) {
395           // This is expected, so don't log it.
396           continue;
397         }
398         convertedConfigValues.put(level, format);
399       }
400       // Merge the default and config-based map; the latter takes priority.
401       final Map<Level, ColorMarker> finalConfigValues = new HashMap<Level, ColorMarker>();
402       finalConfigValues.putAll(defaultValues);
403       finalConfigValues.putAll(convertedConfigValues);
404       // Done; cast as immutable.
405       return ImmutableMap.<Level, ColorMarker> builder()
406                          .putAll(finalConfigValues).build();
407     }
408   }
409 
410   /**
411    * Returns a string property from the Bukkit plugin config.
412    *
413    * @param name
414    *          the desired property.
415    * @param defaultValue
416    *          the fallback value returned by this method.
417    * @return {@code defaultValue} if the Bukkit plugin is not available, or if
418    *         the desired property is not defined in the config.
419    */
420   private static String getStringProperty(final String name,
421                                           final String defaultValue) {
422     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
423       if (BukkitLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; }
424       final String prop = BukkitLoggerAdapter.BUKKIT_PLUGIN.getConfig()
425                                                            .getString(name);
426       return (prop == null) ? defaultValue : prop;
427     }
428   }
429 
430   /**
431    * Converts an SLF4J logging level to a Bukkit logging level.
432    *
433    * <ul>
434    * <li>{@link Level#ERROR} maps to {@link java.util.logging.Level#SEVERE}.</li>
435    * <li>{@link Level#WARN} maps to {@link java.util.logging.Level#WARNING}.</li>
436    * <li>All others map to {@link java.util.logging.Level#INFO} (Bukkit won't
437    * log any messages higher than {@code INFO}).</li>
438    * </ul>
439    *
440    * @param slf4jLevel
441    *          any SLF4J logging level.
442    * @return never null.
443    */
444   private static java.util.logging.Level
445       slf4jLevelIntToBukkitJULLevel(final Level slf4jLevel) {
446     java.util.logging.Level julLevel;
447     switch (slf4jLevel) {
448       case ERROR:
449         julLevel = java.util.logging.Level.SEVERE;
450         break;
451       case WARN:
452         julLevel = java.util.logging.Level.WARNING;
453         break;
454       default:
455         // In Bukkit, Only the SEVERE, WARNING and INFO JUL levels are enabled,
456         // so SLF4J's TRACE and DEBUG levels must be logged at Bukkit's INFO
457         // level.
458         julLevel = java.util.logging.Level.INFO;
459         break;
460     }
461     return julLevel;
462   }
463 
464   /**
465    * Convert YAML logging level properties to SLF4J level objects.
466    *
467    * @param levelStr
468    *          the level property value from the YAML config.
469    * @return null iff the input does not map to a SLF4J logging level name in a
470    *         case-insensitive fashion.
471    */
472   private static Level stringToLevel(final String levelStr) {
473     if ("trace".equalsIgnoreCase(levelStr)) {
474       return Level.TRACE;
475     } else if ("debug".equalsIgnoreCase(levelStr)) {
476       return Level.DEBUG;
477     } else if ("info".equalsIgnoreCase(levelStr)) {
478       return Level.INFO;
479     } else if ("warn".equalsIgnoreCase(levelStr)) {
480       return Level.WARN;
481     } else if ("error".equalsIgnoreCase(levelStr)) {
482       return Level.ERROR;
483     } else {
484       return null;
485     }
486   }
487 
488   @Override
489   public void debug(final Marker marker, final String msg) {
490     if (!this.isDebugEnabled()) { return; }
491     this.log(Level.DEBUG, marker, msg, null);
492   }
493 
494   @Override
495   public void debug(final Marker marker, final String format, final Object arg) {
496     if (!this.isDebugEnabled()) { return; }
497     this.formatAndLog(Level.DEBUG, marker, format, arg, null);
498   }
499 
500   @Override
501   public void debug(final Marker marker, final String format,
502                     final Object... arguments) {
503     if (!this.isDebugEnabled()) { return; }
504     this.formatAndLog(Level.DEBUG, marker, format, arguments);
505   }
506 
507   @Override
508   public void debug(final Marker marker, final String format,
509                     final Object arg1, final Object arg2) {
510     if (!this.isDebugEnabled()) { return; }
511     this.formatAndLog(Level.DEBUG, marker, format, arg1, arg2);
512   }
513 
514   @Override
515   public void debug(final Marker marker, final String msg, final Throwable t) {
516     if (!this.isDebugEnabled()) { return; }
517     this.log(Level.DEBUG, marker, msg, t);
518   }
519 
520   @Override
521   public void debug(final String msg) {
522     if (!this.isDebugEnabled()) { return; }
523     this.log(Level.DEBUG, null, msg, null);
524   }
525 
526   @Override
527   public void debug(final String format, final Object arg) {
528     if (!this.isDebugEnabled()) { return; }
529     this.formatAndLog(Level.DEBUG, null, format, arg, null);
530   }
531 
532   @Override
533   public void debug(final String format, final Object... arguments) {
534     if (!this.isDebugEnabled()) { return; }
535     this.formatAndLog(Level.DEBUG, null, format, arguments);
536   }
537 
538   @Override
539   public void debug(final String format, final Object arg1, final Object arg2) {
540     if (!this.isDebugEnabled()) { return; }
541     this.formatAndLog(Level.DEBUG, null, format, arg1, arg2);
542   }
543 
544   @Override
545   public void debug(final String msg, final Throwable t) {
546     if (!this.isDebugEnabled()) { return; }
547     this.log(Level.DEBUG, null, msg, t);
548   }
549 
550   @Override
551   public void error(final Marker marker, final String msg) {
552     if (!this.isErrorEnabled()) { return; }
553     this.log(Level.ERROR, marker, msg, null);
554   }
555 
556   @Override
557   public void error(final Marker marker, final String format, final Object arg) {
558     if (!this.isErrorEnabled()) { return; }
559     this.formatAndLog(Level.ERROR, marker, format, arg, null);
560   }
561 
562   @Override
563   public void error(final Marker marker, final String format,
564                     final Object... arguments) {
565     if (!this.isErrorEnabled()) { return; }
566     this.formatAndLog(Level.ERROR, marker, format, arguments);
567   }
568 
569   @Override
570   public void error(final Marker marker, final String format,
571                     final Object arg1, final Object arg2) {
572     if (!this.isErrorEnabled()) { return; }
573     this.formatAndLog(Level.ERROR, marker, format, arg1, arg2);
574   }
575 
576   @Override
577   public void error(final Marker marker, final String msg, final Throwable t) {
578     if (!this.isErrorEnabled()) { return; }
579     this.log(Level.ERROR, marker, msg, t);
580   }
581 
582   @Override
583   public void error(final String msg) {
584     if (!this.isErrorEnabled()) { return; }
585     this.log(Level.ERROR, null, msg, null);
586   }
587 
588   @Override
589   public void error(final String format, final Object arg) {
590     if (!this.isErrorEnabled()) { return; }
591     this.formatAndLog(Level.ERROR, null, format, arg, null);
592   }
593 
594   @Override
595   public void error(final String format, final Object... arguments) {
596     if (!this.isErrorEnabled()) { return; }
597     this.formatAndLog(Level.ERROR, null, format, arguments);
598   }
599 
600   @Override
601   public void error(final String format, final Object arg1, final Object arg2) {
602     if (!this.isErrorEnabled()) { return; }
603     this.formatAndLog(Level.ERROR, null, format, arg1, arg2);
604   }
605 
606   @Override
607   public void error(final String msg, final Throwable t) {
608     if (!this.isErrorEnabled()) { return; }
609     this.log(Level.ERROR, null, msg, t);
610   }
611 
612   @Override
613   public String getName() {
614     return this.name;
615   }
616 
617   @Override
618   public void info(final Marker marker, final String msg) {
619     if (!this.isInfoEnabled()) { return; }
620     this.log(Level.INFO, marker, msg, null);
621   }
622 
623   @Override
624   public void info(final Marker marker, final String format, final Object arg) {
625     if (!this.isInfoEnabled()) { return; }
626     this.formatAndLog(Level.INFO, marker, format, arg, null);
627   }
628 
629   @Override
630   public void info(final Marker marker, final String format,
631                    final Object... arguments) {
632     if (!this.isInfoEnabled()) { return; }
633     this.formatAndLog(Level.INFO, marker, format, arguments);
634   }
635 
636   @Override
637   public void info(final Marker marker, final String format, final Object arg1,
638                    final Object arg2) {
639     if (!this.isInfoEnabled()) { return; }
640     this.formatAndLog(Level.INFO, marker, format, arg1, arg2);
641   }
642 
643   @Override
644   public void info(final Marker marker, final String msg, final Throwable t) {
645     if (!this.isInfoEnabled()) { return; }
646     this.log(Level.INFO, marker, msg, t);
647   }
648 
649   @Override
650   public void info(final String msg) {
651     if (!this.isInfoEnabled()) { return; }
652     this.log(Level.INFO, null, msg, null);
653   }
654 
655   @Override
656   public void info(final String format, final Object arg) {
657     if (!this.isInfoEnabled()) { return; }
658     this.formatAndLog(Level.INFO, null, format, arg, null);
659   }
660 
661   @Override
662   public void info(final String format, final Object... arguments) {
663     if (!this.isInfoEnabled()) { return; }
664     this.formatAndLog(Level.INFO, null, format, arguments);
665   }
666 
667   @Override
668   public void info(final String format, final Object arg1, final Object arg2) {
669     if (!this.isInfoEnabled()) { return; }
670     this.formatAndLog(Level.INFO, null, format, arg1, arg2);
671   }
672 
673   @Override
674   public void info(final String msg, final Throwable t) {
675     if (!this.isInfoEnabled()) { return; }
676     this.log(Level.INFO, null, msg, t);
677   }
678 
679   @Override
680   public boolean isDebugEnabled() {
681     return this.isLevelEnabled(Level.DEBUG);
682   }
683 
684   @Override
685   public boolean isDebugEnabled(final Marker marker) {
686     return this.isLevelEnabled(Level.DEBUG);
687   }
688 
689   @Override
690   public boolean isErrorEnabled() {
691     return this.isLevelEnabled(Level.ERROR);
692   }
693 
694   @Override
695   public boolean isErrorEnabled(final Marker marker) {
696     return this.isLevelEnabled(Level.ERROR);
697   }
698 
699   @Override
700   public boolean isInfoEnabled() {
701     return this.isLevelEnabled(Level.INFO);
702   }
703 
704   @Override
705   public boolean isInfoEnabled(final Marker marker) {
706     return this.isLevelEnabled(Level.INFO);
707   }
708 
709   @Override
710   public boolean isTraceEnabled() {
711     return this.isLevelEnabled(Level.TRACE);
712   }
713 
714   @Override
715   public boolean isTraceEnabled(final Marker marker) {
716     return this.isLevelEnabled(Level.TRACE);
717   }
718 
719   @Override
720   public boolean isWarnEnabled() {
721     return this.isLevelEnabled(Level.WARN);
722   }
723 
724   @Override
725   public boolean isWarnEnabled(final Marker marker) {
726     return this.isLevelEnabled(Level.WARN);
727   }
728 
729   @Override
730   public void trace(final Marker marker, final String msg) {
731     if (!this.isTraceEnabled()) { return; }
732     this.log(Level.TRACE, marker, msg, null);
733   }
734 
735   @Override
736   public void trace(final Marker marker, final String format, final Object arg) {
737     if (!this.isTraceEnabled()) { return; }
738     this.formatAndLog(Level.TRACE, marker, format, arg, null);
739   }
740 
741   @Override
742   public void trace(final Marker marker, final String format,
743                     final Object... arguments) {
744     if (!this.isTraceEnabled()) { return; }
745     this.formatAndLog(Level.TRACE, marker, format, arguments);
746   }
747 
748   @Override
749   public void trace(final Marker marker, final String format,
750                     final Object arg1, final Object arg2) {
751     if (!this.isTraceEnabled()) { return; }
752     this.formatAndLog(Level.TRACE, marker, format, arg1, arg2);
753   }
754 
755   @Override
756   public void trace(final Marker marker, final String msg, final Throwable t) {
757     if (!this.isTraceEnabled()) { return; }
758     this.log(Level.TRACE, marker, msg, t);
759   }
760 
761   @Override
762   public void trace(final String msg) {
763     if (!this.isTraceEnabled()) { return; }
764     this.log(Level.TRACE, null, msg, null);
765   }
766 
767   @Override
768   public void trace(final String format, final Object arg) {
769     if (!this.isTraceEnabled()) { return; }
770     this.formatAndLog(Level.TRACE, null, format, arg, null);
771   }
772 
773   @Override
774   public void trace(final String format, final Object... arguments) {
775     if (!this.isTraceEnabled()) { return; }
776     this.formatAndLog(Level.TRACE, null, format, arguments);
777   }
778 
779   @Override
780   public void trace(final String format, final Object arg1, final Object arg2) {
781     if (!this.isTraceEnabled()) { return; }
782     this.formatAndLog(Level.TRACE, null, format, arg1, arg2);
783   }
784 
785   @Override
786   public void trace(final String msg, final Throwable t) {
787     if (!this.isTraceEnabled()) { return; }
788     this.log(Level.TRACE, null, msg, t);
789   }
790 
791   @Override
792   public void warn(final Marker marker, final String msg) {
793     if (!this.isWarnEnabled()) { return; }
794     this.log(Level.WARN, marker, msg, null);
795   }
796 
797   @Override
798   public void warn(final Marker marker, final String format, final Object arg) {
799     if (!this.isWarnEnabled()) { return; }
800     this.formatAndLog(Level.WARN, marker, format, arg, null);
801   }
802 
803   @Override
804   public void warn(final Marker marker, final String format,
805                    final Object... arguments) {
806     if (!this.isWarnEnabled()) { return; }
807     this.formatAndLog(Level.WARN, marker, format, arguments);
808   }
809 
810   @Override
811   public void warn(final Marker marker, final String format, final Object arg1,
812                    final Object arg2) {
813     if (!this.isWarnEnabled()) { return; }
814     this.formatAndLog(Level.WARN, marker, format, arg1, arg2);
815   }
816 
817   @Override
818   public void warn(final Marker marker, final String msg, final Throwable t) {
819     if (!this.isWarnEnabled()) { return; }
820     this.log(Level.WARN, marker, msg, t);
821   }
822 
823   @Override
824   public void warn(final String msg) {
825     if (!this.isWarnEnabled()) { return; }
826     this.log(Level.WARN, null, msg, null);
827   }
828 
829   @Override
830   public void warn(final String format, final Object arg) {
831     if (!this.isWarnEnabled()) { return; }
832     this.formatAndLog(Level.WARN, null, format, arg, null);
833   }
834 
835   @Override
836   public void warn(final String format, final Object... arguments) {
837     if (!this.isWarnEnabled()) { return; }
838     this.formatAndLog(Level.WARN, null, format, arguments);
839   }
840 
841   @Override
842   public void warn(final String format, final Object arg1, final Object arg2) {
843     if (!this.isWarnEnabled()) { return; }
844     this.formatAndLog(Level.WARN, null, format, arg1, arg2);
845   }
846 
847   @Override
848   public void warn(final String msg, final Throwable t) {
849     if (!this.isWarnEnabled()) { return; }
850     this.log(Level.WARN, null, msg, t);
851   }
852 
853   /**
854    * Computes this logger's short name, which is equivalent to the short Java
855    * package name format (e.g. a logger named "info.ronjenkins.bukkit.MyPlugin"
856    * would have a short name of "i.r.b.MyPlugin").
857    *
858    * @return never null.
859    */
860   private String computeShortName() {
861     final List<String> splitName = new ArrayList<String>();
862     splitName.addAll(Arrays.asList(this.name.split("\\.")));
863     final int shortNameLength = ((splitName.size() - 1) * 2)
864                                 + splitName.get(splitName.size() - 1).length();
865     final String finalName = splitName.remove(splitName.size() - 1);
866     final StringBuffer shortName = new StringBuffer(shortNameLength);
867     for (final String part : splitName) {
868       shortName.append(part.charAt(0)).append('.');
869     }
870     shortName.append(finalName);
871     return shortName.toString();
872   }
873 
874   /**
875    * Computes this logger's current logging level, based on the Bukkit plugin
876    * config.
877    *
878    * @return the value of "slf4j.defaultLogLevel" if neither this logger nor any
879    *         of its ancestors define a logging level.
880    */
881   private Level determineCurrentLevel() {
882     // Compute the current level, which may be null.
883     String tempName = this.name;
884     Level level = null;
885     int indexOfLastDot = tempName.length();
886     while ((level == null) && (indexOfLastDot > -1)) {
887       tempName = tempName.substring(0, indexOfLastDot);
888       level = BukkitLoggerAdapter.stringToLevel(BukkitLoggerAdapter.getStringProperty(BukkitLoggerAdapter.CONFIG_KEY_PREFIX_LOG
889                                                                                           + tempName,
890                                                                                       null));
891       indexOfLastDot = String.valueOf(tempName).lastIndexOf(".");
892     }
893     // Return the default value if we got null.
894     return (level == null) ? BukkitLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL
895                           : level;
896   }
897 
898   /**
899    * For formatted messages, first substitute arguments and then log.
900    *
901    * @param level
902    *          the level of this message.
903    * @param marker
904    *          the marker to use for this message, may be null.
905    * @param format
906    *          the message format string.
907    * @param arguments
908    *          3 or more arguments.
909    */
910   private void formatAndLog(final Level level, final Marker marker,
911                             final String format, final Object... arguments) {
912     if (!this.isLevelEnabled(level)) { return; }
913     final FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
914     this.log(level, marker, tp.getMessage(), tp.getThrowable());
915   }
916 
917   /**
918    * For formatted messages, first substitute arguments and then log.
919    *
920    * @param level
921    *          the level of this message.
922    * @param marker
923    *          the marker to use for this message, may be null.
924    * @param format
925    *          the message format string.
926    * @param arg1
927    *          format argument #1.
928    * @param arg2
929    *          format argument #2.
930    */
931   private void formatAndLog(final Level level, final Marker marker,
932                             final String format, final Object arg1,
933                             final Object arg2) {
934     if (!this.isLevelEnabled(level)) { return; }
935     final FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
936     this.log(level, marker, tp.getMessage(), tp.getThrowable());
937   }
938 
939   /**
940    * Is the given log level currently enabled?
941    *
942    * @param logLevel
943    *          is this level enabled?
944    * @return true if enabled, false if disabled.
945    */
946   private boolean isLevelEnabled(final Level logLevel) {
947     // Ensure that SLF4Bukkit is initialized. Every public API call passes
948     // through this method, so this is the appropriate place to ensure
949     // initialization.
950     BukkitLoggerAdapter.init(false);
951     // log level are numerically ordered so can use simple numeric comparison
952     //
953     // the PLUGIN.getLogger().isLoggable() check avoids the unconditional
954     // construction of location data for disabled log statements. As of
955     // 2008-07-31, callers of this method do not perform this check. See also
956     // http://jira.qos.ch/browse/SLF4J-81
957     final Level currentLogLevel = this.determineCurrentLevel();
958     return (logLevel.toInt() >= currentLogLevel.toInt())
959            && (BukkitLoggerAdapter.getBukkitLogger().isLoggable(BukkitLoggerAdapter.slf4jLevelIntToBukkitJULLevel(logLevel)));
960   }
961 
962   /**
963    * Assembles the final log message and sends it to the appropriate Bukkit
964    * logger.
965    *
966    * @param level
967    *          the desired log level of the message.
968    * @param marker
969    *          the marker to use for this message, may be null.
970    * @param message
971    *          the message to be logged.
972    * @param throwable
973    *          the exception to be logged, may be null.
974    */
975   private void log(final Level level, final Marker marker,
976                    final String message, final Throwable throwable) {
977     final java.util.logging.Logger logger;
978     synchronized (BukkitLoggerAdapter.INITIALIZATION_LOCK) {
979       // Ensure that the logger will accept this request.
980       if (!this.isLevelEnabled(level)) { return; }
981       // Determine which logger will be used.
982       logger = BukkitLoggerAdapter.getBukkitLogger();
983     }
984 
985     // Start building the log message.
986     final StringBuilder buf = new StringBuilder(32);
987     boolean hasHeader = false;
988 
989     // Use the marker, if applicable. Otherwise, use the default color for
990     // this level.
991     if (marker instanceof ColorMarker) {
992       buf.append(((ColorMarker) marker).getValue());
993     } else {
994       buf.append(BukkitLoggerAdapter.CONFIG_VALUE_LEVEL_COLORS.get(level)
995                                                               .getValue());
996     }
997 
998     // Indicate that this message comes from SLF4J, if desired.
999     if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_HEADER) {
1000       hasHeader = true;
1001       buf.append("[SLF4J]");
1002     }
1003 
1004     // Print a readable representation of the log level, but only for log levels
1005     // that Bukkit would otherwise eat.
1006     switch (level) {
1007       case TRACE:
1008         hasHeader = true;
1009         buf.append("[TRACE]");
1010         break;
1011       case DEBUG:
1012         hasHeader = true;
1013         buf.append("[DEBUG]");
1014         break;
1015       default:
1016         break;
1017     }
1018 
1019     // Append the current thread name, if desired.
1020     if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME) {
1021       hasHeader = true;
1022       buf.append('[');
1023       buf.append(Thread.currentThread().getName());
1024       buf.append("]");
1025     }
1026 
1027     // Buffer the current output with a space, unless there is no output.
1028     if (hasHeader) {
1029       buf.append(' ');
1030     }
1031 
1032     // Append the name of the log instance, if desired.
1033     if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME) {
1034       buf.append('{').append(this.name).append("} ");
1035     } else if (BukkitLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME) {
1036       if (this.shortLogName == null) {
1037         this.shortLogName = this.computeShortName();
1038       }
1039       buf.append('{').append(this.shortLogName).append("} ");
1040     }
1041 
1042     // Append the message.
1043     buf.append(message);
1044 
1045     // Append the throwable, if applicable.
1046     if (throwable != null) {
1047       buf.append('\n').append(ExceptionUtils.getFullStackTrace(throwable)
1048                                             .trim());
1049     }
1050 
1051     // Append a reset directive.
1052     buf.append(ChatColor.RESET);
1053 
1054     // Log the message.
1055     logger.log(BukkitLoggerAdapter.slf4jLevelIntToBukkitJULLevel(level),
1056                mapper.map(buf.toString()));
1057   }
1058 }