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