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