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 java.io.IOException;
48  import java.io.InputStream;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.logging.LogRecord;
54  import java.util.logging.Logger;
55  
56  import org.bukkit.Bukkit;
57  import org.bukkit.plugin.Plugin;
58  import org.slf4j.event.Level;
59  import org.slf4j.helpers.FormattingTuple;
60  import org.slf4j.helpers.MarkerIgnoringBase;
61  import org.slf4j.helpers.MessageFormatter;
62  import org.yaml.snakeyaml.Yaml;
63  
64  /**
65   * <p>
66   * A merger of SLF4J's {@code SimpleLogger} and {@code JDK14LoggerAdapter},
67   * wired to log all messages to the enclosing Bukkit plugin. The plugin is
68   * identified by reading the "name" attribute from {@code plugin.yml} in the
69   * current classloader.
70   * </p>
71   *
72   * <p>
73   * Plugins that include SLF4Bukkit can use the following values in
74   * {@code config.yml} to configure the behavior of SLF4Bukkit. SLF4Bukkit uses
75   * Bukkit's plugin configuration API to retrieve config values, so both on-disk
76   * and built-in {@code config.yml} behavior is supported.
77   * </p>
78   *
79   * <ul>
80   * <li><code>slf4j.defaultLogLevel</code> - Default log level for all SLF4Bukkit
81   * loggers in this plugin. Must be one of "trace", "debug", "info", "warn", or
82   * "error". Defaults to "info".</li>
83   *
84   * <li><code>slf4j.log.<em>a.b.c</em></code> - Logging detail level for an
85   * SLF4Bukkit logger instance in this plugin named "a.b.c". Right-side value
86   * must be one of "trace", "debug", "info", "warn", or "error". When a logger
87   * named "a.b.c" is initialized, its level is assigned from this property. If
88   * unspecified, the level of the nearest parent logger will be used. If no
89   * parent logger level is set, then the value specified by
90   * <code>slf4j.defaultLogLevel</code> for this plugin will be used.</li>
91   *
92   * <li><code>slf4j.showHeader</code> -Set to <code>true</code> if you want to
93   * output the {@code [SLF4J]} header. Defaults to <code>false</code>.</li>
94   *
95   * <li><code>slf4j.showThreadName</code> -Set to <code>true</code> if you want
96   * to output the current thread name. Defaults to <code>false</code>.</li>
97   *
98   * <li><code>slf4j.showLogName</code> - Set to <code>true</code> if you want the
99   * logger instance name to be included in output messages. Defaults to
100  * <code>false</code>.</li>
101  *
102  * <li><code>slf4j.showShortLogName</code> - Set to <code>true</code> if you
103  * want the logger instance's short name to be included in output messages. The
104  * short name is equal to the full name with every dot-separated portion of the
105  * full name (except the last portion) truncated to its first character.
106  * Defaults to <code>true</code>.</li>
107  * </ul>
108  *
109  * <p>
110  * SLF4J messages at level {@code TRACE} or {@code DEBUG} are logged to Bukkit
111  * at level {@code INFO} because Bukkit does not enable any levels higher than
112  * {@code INFO}. Therefore, only SLF4J messages at level {@code TRACE} or
113  * {@code DEBUG} show their SLF4J level in the message that is logged to the
114  * server console.
115  * </p>
116  *
117  * <p>
118  * Because SLF4Bukkit's configuration comes from the plugin configuration,
119  * SLF4Bukkit supports configuration reloading. To achieve this, call
120  * {@link #init(boolean)} with argument {@code true} after calling
121  * {@link Plugin#reloadConfig()}.
122  * </p>
123  *
124  * <p>
125  * It is possible for SLF4J loggers to be used before the plugin is registered
126  * with Bukkit's plugin manager. SLF4Bukkit is considered to be
127  * <i>uninitialized</i> as long as the plugin cannot be retrieved from Bukkit's
128  * plugin manager. While in the uninitialized state, SLF4Bukkit:
129  * </p>
130  *
131  * <ul>
132  * <li>uses {@link Bukkit#getLogger()} instead of {@link Plugin#getLogger()}.</li>
133  * <li>uses the default configuration values (see above).</li>
134  * <li>attempts to initialize itself upon every logging call until the plugin is
135  * retrievable from Bukkit's plugin manager, at which point SLF4Bukkit is
136  * considered to be <i>initialized</i>. Once initialized,
137  * {@link Plugin#getLogger()} and {@link Plugin#getConfig() the plugin YAML
138  * configuration values} are used.</li>
139  * </ul>
140  *
141  * <p>
142  * For this reason, it is strongly recommended that you not emit any log
143  * messages via SLF4Bukkit until your plugin's {@link Plugin#onLoad() onLoad()}
144  * method has begun execution. (You can safely log messages inside the
145  * {@code onLoad()} method, because your plugin is registered by that time.)
146  * Logging inside static initializers, the plugin class constructor and other
147  * pre-plugin-registration areas of your code is discouraged.
148  * </p>
149  *
150  * <p>
151  * With no configuration, the default output includes the logger short name and
152  * the message, followed by the line separator for the host.
153  * </p>
154  *
155  * @author Ceki G&uuml;lc&uuml;
156  * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
157  * @author Rod Waldhoff
158  * @author Robert Burrell Donkin
159  * @author C&eacute;drik LIME
160  * @author Peter Royal
161  * @author Ronald Jack Jenkins Jr.
162  */
163 public final class BukkitPluginLoggerAdapter extends MarkerIgnoringBase {
164 
165   // Plugin reference.
166   private static transient Plugin BUKKIT_PLUGIN;
167   private static transient String BUKKIT_PLUGIN_NAME;
168   // Constants for JUL record creation.
169   private static final String     CLASS_SELF                          = BukkitPluginLoggerAdapter.class.getName();
170   private static final String     CLASS_SUPER                         = MarkerIgnoringBase.class.getName();
171   // Configuration parameters.
172   private static final String     CONFIG_FALLBACK_DEFAULT_LOG_LEVEL   = "info";
173   private static final boolean    CONFIG_FALLBACK_SHOW_HEADER         = false;
174   private static final boolean    CONFIG_FALLBACK_SHOW_LOG_NAME       = false;
175   private static final boolean    CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME = true;
176   private static final boolean    CONFIG_FALLBACK_SHOW_THREAD_NAME    = false;
177   private static final String     CONFIG_KEY_DEFAULT_LOG_LEVEL        = "slf4j.defaultLogLevel";
178   private static final String     CONFIG_KEY_PREFIX_LOG               = "slf4j.log.";
179   private static final String     CONFIG_KEY_SHOW_HEADER              = "slf4j.showHeader";
180   private static final String     CONFIG_KEY_SHOW_LOG_NAME            = "slf4j.showLogName";
181   private static final String     CONFIG_KEY_SHOW_SHORT_LOG_NAME      = "slf4j.showShortLogName";
182   private static final String     CONFIG_KEY_SHOW_THREAD_NAME         = "slf4j.showThreadName";
183   private static Level            CONFIG_VALUE_DEFAULT_LOG_LEVEL;
184   private static boolean          CONFIG_VALUE_SHOW_HEADER;
185   private static boolean          CONFIG_VALUE_SHOW_LOG_NAME;
186   private static boolean          CONFIG_VALUE_SHOW_SHORT_LOG_NAME;
187   private static boolean          CONFIG_VALUE_SHOW_THREAD_NAME;
188   // Initialization lock.
189   private static final Object     INITIALIZATION_LOCK                 = new Object();
190   // serialVersionUID
191   private static final long       serialVersionUID                    = -2270127287235697381L;
192   // The short name of this simple log instance
193   private transient String        shortLogName                        = null;
194 
195   // NOTE: BukkitPluginLoggerAdapter constructor should have only package access
196   // so that only BukkitPluginLoggerFactory be able to create one.
197   BukkitPluginLoggerAdapter(final String name) {
198     this.name = name;
199   }
200 
201   /**
202    * (Re)initializes all SLF4Bukkit loggers, relying on the YAML configuration
203    * of the enclosing plugin.
204    *
205    * @param reinitialize
206    *          set to {@code true} to reinitialize all loggers, e.g. after
207    *          reloading the plugin config.
208    */
209   public static void init(final boolean reinitialize) {
210     synchronized (BukkitPluginLoggerAdapter.INITIALIZATION_LOCK) {
211       // Do not re-initialize unless requested.
212       if (reinitialize) {
213         BukkitPluginLoggerAdapter.BUKKIT_PLUGIN = null;
214         BukkitPluginLoggerAdapter.BUKKIT_PLUGIN_NAME = null;
215       } else if (BukkitPluginLoggerAdapter.BUKKIT_PLUGIN != null) { return; }
216       // Get a reference to the plugin in this classloader.
217       if (BukkitPluginLoggerAdapter.BUKKIT_PLUGIN_NAME == null) {
218         InputStream pluginYmlFile = null;
219         try {
220           pluginYmlFile = BukkitPluginLoggerAdapter.class.getClassLoader()
221                                                          .getResource("plugin.yml")
222                                                          .openStream();
223           final Yaml yaml = new Yaml();
224           @SuppressWarnings("rawtypes")
225           final Map pluginYml = (Map) yaml.load(pluginYmlFile);
226           BukkitPluginLoggerAdapter.BUKKIT_PLUGIN_NAME = (String) pluginYml.get("name");
227         } catch (final IOException e) {
228           throw new IllegalStateException(e);
229         } finally {
230           if (pluginYmlFile != null) {
231             try {
232               pluginYmlFile.close();
233             } catch (final IOException e) {
234               e.printStackTrace();
235             }
236           }
237         }
238       }
239       // Try to get the plugin. The logging system will be considered
240       // uninitialized until this becomes non-null. While it is null, the Bukkit
241       // server logger will be used instead of the plugin logger, and all
242       // default configuration options will be used.
243       BukkitPluginLoggerAdapter.BUKKIT_PLUGIN = Bukkit.getPluginManager()
244                                                       .getPlugin(BukkitPluginLoggerAdapter.BUKKIT_PLUGIN_NAME);
245       // Get the configuration values.
246       // 1. Look in the plugin's on-disk config.
247       // 2. If the value is absent, use the plugin's built-in config.
248       // 3. If the value is absent, use the default values hardcoded above.
249       // (1 and 2 are handled by using the Bukkit API.)
250       BukkitPluginLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL = BukkitPluginLoggerAdapter.stringToLevel(BukkitPluginLoggerAdapter.getStringProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_DEFAULT_LOG_LEVEL,
251                                                                                                                                                      BukkitPluginLoggerAdapter.CONFIG_FALLBACK_DEFAULT_LOG_LEVEL));
252       BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_HEADER = BukkitPluginLoggerAdapter.getBooleanProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_SHOW_HEADER,
253                                                                                                         BukkitPluginLoggerAdapter.CONFIG_FALLBACK_SHOW_HEADER);
254       BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME = BukkitPluginLoggerAdapter.getBooleanProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_SHOW_LOG_NAME,
255                                                                                                           BukkitPluginLoggerAdapter.CONFIG_FALLBACK_SHOW_LOG_NAME);
256       BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME = BukkitPluginLoggerAdapter.getBooleanProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_SHOW_SHORT_LOG_NAME,
257                                                                                                                 BukkitPluginLoggerAdapter.CONFIG_FALLBACK_SHOW_SHORT_LOG_NAME);
258       BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME = BukkitPluginLoggerAdapter.getBooleanProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_SHOW_THREAD_NAME,
259                                                                                                              BukkitPluginLoggerAdapter.CONFIG_FALLBACK_SHOW_THREAD_NAME);
260     }
261   }
262 
263   private static boolean getBooleanProperty(final String name,
264                                             final boolean defaultValue) {
265     synchronized (BukkitPluginLoggerAdapter.INITIALIZATION_LOCK) {
266       if (BukkitPluginLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; }
267       final String prop = BukkitPluginLoggerAdapter.BUKKIT_PLUGIN.getConfig()
268                                                                  .getString(name);
269       return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop);
270     }
271   }
272 
273   /**
274    * Returns the most appropriate logger.
275    *
276    * @return the logger for the plugin if available; otherwise the server
277    *         logger. Never null.
278    */
279   private static Logger getBukkitLogger() {
280     synchronized (BukkitPluginLoggerAdapter.INITIALIZATION_LOCK) {
281       return BukkitPluginLoggerAdapter.BUKKIT_PLUGIN == null ? Bukkit.getLogger()
282                                                             : BukkitPluginLoggerAdapter.BUKKIT_PLUGIN.getLogger();
283     }
284   }
285 
286   private static String getStringProperty(final String name,
287                                           final String defaultValue) {
288     synchronized (BukkitPluginLoggerAdapter.INITIALIZATION_LOCK) {
289       if (BukkitPluginLoggerAdapter.BUKKIT_PLUGIN == null) { return defaultValue; }
290       final String prop = BukkitPluginLoggerAdapter.BUKKIT_PLUGIN.getConfig()
291                                                                  .getString(name);
292       return (prop == null) ? defaultValue : prop;
293     }
294   }
295 
296   private static java.util.logging.Level
297       slf4jLevelIntToBukkitJULLevel(final Level slf4jLevel) {
298     java.util.logging.Level julLevel;
299     switch (slf4jLevel) {
300     // In Bukkit, Only the SEVERE, WARNING and INFO JUL levels are enabled, so
301     // SLF4J's TRACE and DEBUG levels must be logged at Bukkit's INFO level.
302       case TRACE:
303       case DEBUG:
304       case INFO:
305         julLevel = java.util.logging.Level.INFO;
306         break;
307       case WARN:
308         julLevel = java.util.logging.Level.WARNING;
309         break;
310       case ERROR:
311         julLevel = java.util.logging.Level.SEVERE;
312         break;
313       default:
314         throw new IllegalStateException("Level " + slf4jLevel
315                                         + " is not recognized.");
316     }
317     return julLevel;
318   }
319 
320   /*
321    * Logger API implementations
322    */
323 
324   private static Level stringToLevel(final String levelStr) {
325     if ("trace".equalsIgnoreCase(levelStr)) {
326       return Level.TRACE;
327     } else if ("debug".equalsIgnoreCase(levelStr)) {
328       return Level.DEBUG;
329     } else if ("info".equalsIgnoreCase(levelStr)) {
330       return Level.INFO;
331     } else if ("warn".equalsIgnoreCase(levelStr)) {
332       return Level.WARN;
333     } else if ("error".equalsIgnoreCase(levelStr)) {
334       return Level.ERROR;
335     } else {
336       return Level.INFO;
337     }
338   }
339 
340   /**
341    * A simple implementation which logs messages of level DEBUG according
342    * to the format outlined above.
343    */
344   @Override
345   public void debug(final String msg) {
346     if (!this.isDebugEnabled()) { return; }
347     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.DEBUG, msg, null);
348   }
349 
350   /**
351    * Perform single parameter substitution before logging the message of level
352    * DEBUG according to the format outlined above.
353    */
354   @Override
355   public void debug(final String format, final Object param1) {
356     if (!this.isDebugEnabled()) { return; }
357     this.formatAndLog(Level.DEBUG, format, param1, null);
358   }
359 
360   /**
361    * Perform double parameter substitution before logging the message of level
362    * DEBUG according to the format outlined above.
363    */
364   @Override
365   public void debug(final String format, final Object... argArray) {
366     if (!this.isDebugEnabled()) { return; }
367     this.formatAndLog(Level.DEBUG, format, argArray);
368   }
369 
370   /**
371    * Perform double parameter substitution before logging the message of level
372    * DEBUG according to the format outlined above.
373    */
374   @Override
375   public void debug(final String format, final Object param1,
376                     final Object param2) {
377     if (!this.isDebugEnabled()) { return; }
378     this.formatAndLog(Level.DEBUG, format, param1, param2);
379   }
380 
381   /** Log a message of level DEBUG, including an exception. */
382   @Override
383   public void debug(final String msg, final Throwable t) {
384     if (!this.isDebugEnabled()) { return; }
385     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.DEBUG, msg, t);
386   }
387 
388   /**
389    * A simple implementation which always logs messages of level ERROR according
390    * to the format outlined above.
391    */
392   @Override
393   public void error(final String msg) {
394     if (!this.isErrorEnabled()) { return; }
395     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.ERROR, msg, null);
396   }
397 
398   /**
399    * Perform single parameter substitution before logging the message of level
400    * ERROR according to the format outlined above.
401    */
402   @Override
403   public void error(final String format, final Object arg) {
404     if (!this.isErrorEnabled()) { return; }
405     this.formatAndLog(Level.ERROR, format, arg, null);
406   }
407 
408   /**
409    * Perform double parameter substitution before logging the message of level
410    * ERROR according to the format outlined above.
411    */
412   @Override
413   public void error(final String format, final Object... argArray) {
414     if (!this.isErrorEnabled()) { return; }
415     this.formatAndLog(Level.ERROR, format, argArray);
416   }
417 
418   /**
419    * Perform double parameter substitution before logging the message of level
420    * ERROR according to the format outlined above.
421    */
422   @Override
423   public void error(final String format, final Object arg1, final Object arg2) {
424     if (!this.isErrorEnabled()) { return; }
425     this.formatAndLog(Level.ERROR, format, arg1, arg2);
426   }
427 
428   /** Log a message of level ERROR, including an exception. */
429   @Override
430   public void error(final String msg, final Throwable t) {
431     if (!this.isErrorEnabled()) { return; }
432     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.ERROR, msg, t);
433   }
434 
435   /**
436    * A simple implementation which logs messages of level INFO according
437    * to the format outlined above.
438    */
439   @Override
440   public void info(final String msg) {
441     if (!this.isInfoEnabled()) { return; }
442     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.INFO, msg, null);
443   }
444 
445   /**
446    * Perform single parameter substitution before logging the message of level
447    * INFO according to the format outlined above.
448    */
449   @Override
450   public void info(final String format, final Object arg) {
451     if (!this.isInfoEnabled()) { return; }
452     this.formatAndLog(Level.INFO, format, arg, null);
453   }
454 
455   /**
456    * Perform double parameter substitution before logging the message of level
457    * INFO according to the format outlined above.
458    */
459   @Override
460   public void info(final String format, final Object... argArray) {
461     if (!this.isInfoEnabled()) { return; }
462     this.formatAndLog(Level.INFO, format, argArray);
463   }
464 
465   /**
466    * Perform double parameter substitution before logging the message of level
467    * INFO according to the format outlined above.
468    */
469   @Override
470   public void info(final String format, final Object arg1, final Object arg2) {
471     if (!this.isInfoEnabled()) { return; }
472     this.formatAndLog(Level.INFO, format, arg1, arg2);
473   }
474 
475   /** Log a message of level INFO, including an exception. */
476   @Override
477   public void info(final String msg, final Throwable t) {
478     if (!this.isInfoEnabled()) { return; }
479     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.INFO, msg, t);
480   }
481 
482   /** Are {@code debug} messages currently enabled? */
483   @Override
484   public boolean isDebugEnabled() {
485     return this.isLevelEnabled(Level.DEBUG);
486   }
487 
488   /** Are {@code error} messages currently enabled? */
489   @Override
490   public boolean isErrorEnabled() {
491     return this.isLevelEnabled(Level.ERROR);
492   }
493 
494   /** Are {@code info} messages currently enabled? */
495   @Override
496   public boolean isInfoEnabled() {
497     return this.isLevelEnabled(Level.INFO);
498   }
499 
500   /** Are {@code trace} messages currently enabled? */
501   @Override
502   public boolean isTraceEnabled() {
503     return this.isLevelEnabled(Level.TRACE);
504   }
505 
506   /** Are {@code warn} messages currently enabled? */
507   @Override
508   public boolean isWarnEnabled() {
509     return this.isLevelEnabled(Level.WARN);
510   }
511 
512   /**
513    * A simple implementation which logs messages of level TRACE according
514    * to the format outlined above.
515    */
516   @Override
517   public void trace(final String msg) {
518     if (!this.isTraceEnabled()) { return; }
519     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.TRACE, msg, null);
520   }
521 
522   /**
523    * Perform single parameter substitution before logging the message of level
524    * TRACE according to the format outlined above.
525    */
526   @Override
527   public void trace(final String format, final Object param1) {
528     if (!this.isTraceEnabled()) { return; }
529     this.formatAndLog(Level.TRACE, format, param1, null);
530   }
531 
532   /**
533    * Perform double parameter substitution before logging the message of level
534    * TRACE according to the format outlined above.
535    */
536   @Override
537   public void trace(final String format, final Object... argArray) {
538     if (!this.isTraceEnabled()) { return; }
539     this.formatAndLog(Level.TRACE, format, argArray);
540   }
541 
542   /**
543    * Perform double parameter substitution before logging the message of level
544    * TRACE according to the format outlined above.
545    */
546   @Override
547   public void trace(final String format, final Object param1,
548                     final Object param2) {
549     if (!this.isTraceEnabled()) { return; }
550     this.formatAndLog(Level.TRACE, format, param1, param2);
551   }
552 
553   /** Log a message of level TRACE, including an exception. */
554   @Override
555   public void trace(final String msg, final Throwable t) {
556     if (!this.isTraceEnabled()) { return; }
557     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.TRACE, msg, t);
558   }
559 
560   /**
561    * A simple implementation which always logs messages of level WARN according
562    * to the format outlined above.
563    */
564   @Override
565   public void warn(final String msg) {
566     if (!this.isWarnEnabled()) { return; }
567     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.WARN, msg, null);
568   }
569 
570   /**
571    * Perform single parameter substitution before logging the message of level
572    * WARN according to the format outlined above.
573    */
574   @Override
575   public void warn(final String format, final Object arg) {
576     if (!this.isWarnEnabled()) { return; }
577     this.formatAndLog(Level.WARN, format, arg, null);
578   }
579 
580   /**
581    * Perform double parameter substitution before logging the message of level
582    * WARN according to the format outlined above.
583    */
584   @Override
585   public void warn(final String format, final Object... argArray) {
586     if (!this.isWarnEnabled()) { return; }
587     this.formatAndLog(Level.WARN, format, argArray);
588   }
589 
590   /**
591    * Perform double parameter substitution before logging the message of level
592    * WARN according to the format outlined above.
593    */
594   @Override
595   public void warn(final String format, final Object arg1, final Object arg2) {
596     if (!this.isWarnEnabled()) { return; }
597     this.formatAndLog(Level.WARN, format, arg1, arg2);
598   }
599 
600   /*
601    * Logic from SimpleLogger/JDK14LoggerAdapter
602    */
603 
604   /** Log a message of level WARN, including an exception. */
605   @Override
606   public void warn(final String msg, final Throwable t) {
607     if (!this.isWarnEnabled()) { return; }
608     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, Level.WARN, msg, t);
609   }
610 
611   private String computeShortName() {
612     final List<String> splitName = new ArrayList<String>();
613     splitName.addAll(Arrays.asList(this.name.split("\\.")));
614     final int shortNameLength = ((splitName.size() - 1) * 2)
615                                 + splitName.get(splitName.size() - 1).length();
616     final String finalName = splitName.remove(splitName.size() - 1);
617     final StringBuffer shortName = new StringBuffer(shortNameLength);
618     for (final String part : splitName) {
619       shortName.append(part.charAt(0)).append('.');
620     }
621     shortName.append(finalName);
622     return shortName.toString();
623   }
624 
625   private Level determineCurrentLevel() {
626     final String levelString = this.recursivelyComputeLevelString();
627     if (levelString != null) {
628       return BukkitPluginLoggerAdapter.stringToLevel(levelString);
629     } else {
630       return BukkitPluginLoggerAdapter.CONFIG_VALUE_DEFAULT_LOG_LEVEL;
631     }
632   }
633 
634   /**
635    * For formatted messages, first substitute arguments and then log.
636    *
637    * @param level
638    * @param format
639    * @param arguments
640    *          a list of 3 ore more arguments
641    */
642   private void formatAndLog(final Level level, final String format,
643                             final Object... arguments) {
644     if (!this.isLevelEnabled(level)) { return; }
645     final FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
646     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, level, tp.getMessage(),
647              tp.getThrowable());
648   }
649 
650   /**
651    * For formatted messages, first substitute arguments and then log.
652    *
653    * @param level
654    * @param format
655    * @param arg1
656    * @param arg2
657    */
658   private void formatAndLog(final Level level, final String format,
659                             final Object arg1, final Object arg2) {
660     if (!this.isLevelEnabled(level)) { return; }
661     final FormattingTuple tp = MessageFormatter.format(format, arg1, arg2);
662     this.log(BukkitPluginLoggerAdapter.CLASS_SELF, level, tp.getMessage(),
663              tp.getThrowable());
664   }
665 
666   /**
667    * Is the given log level currently enabled?
668    *
669    * @param logLevel
670    *          is this level enabled?
671    */
672   private boolean isLevelEnabled(final Level logLevel) {
673     // Ensure that SLF4Bukkit is initialized. Every public API call passes
674     // through this method, so this is the appropriate place to ensure
675     // initialization.
676     BukkitPluginLoggerAdapter.init(false);
677     // log level are numerically ordered so can use simple numeric comparison
678     //
679     // the PLUGIN.getLogger().isLoggable() check avoids the unconditional
680     // construction of location data for disabled log statements. As of
681     // 2008-07-31, callers of this method do not perform this check. See also
682     // http://jira.qos.ch/browse/SLF4J-81
683     final Level currentLogLevel = this.determineCurrentLevel();
684     return (logLevel.toInt() >= currentLogLevel.toInt())
685            && (BukkitPluginLoggerAdapter.getBukkitLogger().isLoggable(BukkitPluginLoggerAdapter.slf4jLevelIntToBukkitJULLevel(logLevel)));
686   }
687 
688   /**
689    * Fill in caller data if possible.
690    *
691    * @param record
692    *          The record to update
693    */
694   private void
695       julFillCallerData(final String callerFQCN, final LogRecord record) {
696     final StackTraceElement[] steArray = new Throwable().getStackTrace();
697 
698     int selfIndex = -1;
699     for (int i = 0; i < steArray.length; i++) {
700       final String className = steArray[i].getClassName();
701       if (className.equals(callerFQCN)
702           || className.equals(BukkitPluginLoggerAdapter.CLASS_SUPER)) {
703         selfIndex = i;
704         break;
705       }
706     }
707 
708     int found = -1;
709     for (int i = selfIndex + 1; i < steArray.length; i++) {
710       final String className = steArray[i].getClassName();
711       if (!(className.equals(callerFQCN) || className.equals(BukkitPluginLoggerAdapter.CLASS_SUPER))) {
712         found = i;
713         break;
714       }
715     }
716 
717     if (found != -1) {
718       final StackTraceElement ste = steArray[found];
719       // setting the class name has the side effect of setting
720       // the needToInferCaller variable to false.
721       record.setSourceClassName(ste.getClassName());
722       record.setSourceMethodName(ste.getMethodName());
723     }
724   }
725 
726   /**
727    * Log the message at the specified level with the specified throwable if any.
728    * This method creates a LogRecord and fills in caller date before calling
729    * this instance's JDK14 logger.
730    *
731    * See bug report #13 for more details.
732    *
733    * @param logger
734    * @param level
735    * @param msg
736    * @param t
737    */
738   private void julLog(final Logger logger, final String callerFQCN,
739                       final java.util.logging.Level level, final String msg,
740                       final Throwable t) {
741     // millis and thread are filled by the constructor
742     final LogRecord record = new LogRecord(level, msg);
743     record.setLoggerName(this.getName());
744     record.setThrown(t);
745     // Note: parameters in record are not set because SLF4J only
746     // supports a single formatting style
747     this.julFillCallerData(callerFQCN, record);
748     logger.log(record);
749   }
750 
751   /**
752    * This is our internal implementation for logging regular (non-parameterized)
753    * log messages.
754    *
755    * @param callerFQCN
756    *          the FQCN of the class that is calling the logger
757    * @param level
758    *          One of the LOG_LEVEL_XXX constants defining the log level
759    * @param message
760    *          The message itself
761    * @param t
762    *          The exception whose stack trace should be logged
763    */
764   private void log(final String callerFQCN, final Level level,
765                    final String message, final Throwable t) {
766     final Logger logger;
767     synchronized (BukkitPluginLoggerAdapter.INITIALIZATION_LOCK) {
768       // Ensure that the logger will accept this request.
769       if (!this.isLevelEnabled(level)) { return; }
770       // Determine which logger will be used.
771       logger = BukkitPluginLoggerAdapter.getBukkitLogger();
772     }
773 
774     // Prepare message
775     final StringBuilder buf = new StringBuilder(32);
776 
777     // Indicate that this message comes from SLF4J
778     if (BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_HEADER) {
779       buf.append("SLF4J");
780     }
781 
782     // Print a readable representation of the log level (but only for log levels
783     // that Bukkit would otherwise eat)
784     switch (level) {
785       case TRACE:
786         buf.append("[TRACE]");
787         break;
788       case DEBUG:
789         buf.append("[DEBUG]");
790         break;
791       default:
792         break;
793     }
794 
795     // Append current thread name if so configured
796     if (BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_THREAD_NAME) {
797       buf.append('[');
798       buf.append(Thread.currentThread().getName());
799       buf.append("]");
800     }
801 
802     // Buffer the current output with a space, unless there is no output.
803     if (buf.length() > 0) {
804       buf.append(' ');
805     }
806 
807     // Append the name of the log instance if so configured
808     if (BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_SHORT_LOG_NAME) {
809       if (this.shortLogName == null) {
810         this.shortLogName = this.computeShortName();
811       }
812       buf.append(String.valueOf(this.shortLogName)).append(" - ");
813     } else if (BukkitPluginLoggerAdapter.CONFIG_VALUE_SHOW_LOG_NAME) {
814       buf.append(String.valueOf(this.name)).append(" - ");
815     }
816 
817     // Append the message
818     buf.append(message);
819 
820     // Log to Bukkit
821     this.julLog(logger, BukkitPluginLoggerAdapter.CLASS_SELF,
822                 BukkitPluginLoggerAdapter.slf4jLevelIntToBukkitJULLevel(level),
823                 buf.toString(), t);
824   }
825 
826   private String recursivelyComputeLevelString() {
827     String tempName = this.name;
828     String levelString = null;
829     int indexOfLastDot = tempName.length();
830     while ((levelString == null) && (indexOfLastDot > -1)) {
831       tempName = tempName.substring(0, indexOfLastDot);
832       levelString = BukkitPluginLoggerAdapter.getStringProperty(BukkitPluginLoggerAdapter.CONFIG_KEY_PREFIX_LOG
833                                                                     + tempName,
834                                                                 null);
835       indexOfLastDot = String.valueOf(tempName).lastIndexOf(".");
836     }
837     return levelString;
838   }
839 
840 }