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