View Javadoc
1   package org.opentrafficsim.base.logger;
2   
3   import java.util.Arrays;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Map;
7   import java.util.Set;
8   
9   import org.pmw.tinylog.Configurator;
10  import org.pmw.tinylog.Level;
11  import org.pmw.tinylog.LogEntryForwarder;
12  import org.pmw.tinylog.writers.ConsoleWriter;
13  import org.pmw.tinylog.writers.Writer;
14  
15  /**
16   * The CategoryLogger can log for specific Categories. The way to call the logger for messages that always need to be logged,
17   * such as an error with an exception is:
18   * 
19   * <pre>
20   * CategoryLogger.always().error(exception, "Parameter {} did not initialize correctly", param1.toString());
21   * </pre>
22   * 
23   * It is also possible to indicate the category / categories for the message, which will only be logged if at least one of the
24   * indicated categories is turned on with addLogCategory() or setLogCategories(), or if one of the added or set LogCategories is
25   * LogCategory.ALL:
26   * 
27   * <pre>
28   * CategoryLogger.filter(Cat.BASE).debug("Parameter {} initialized correctly", param1.toString());
29   * </pre>
30   * 
31   * Copyright (c) 2003-2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
32   * for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>. The
33   * source code and binary code of this software is proprietary information of Delft University of Technology.
34   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
35   */
36  @SuppressWarnings({ "checkstyle:visibilitymodifier", "checkstyle:finalclass", "checkstyle:needbraces" })
37  public class CategoryLogger
38  {
39      /** The default message format. */
40      public static final String DEFAULT_MESSAGE_FORMAT = "{class_name}.{method}:{line} {message|indent=4}";
41  
42      /** The current message format. */
43      private static String defaultMessageFormat = DEFAULT_MESSAGE_FORMAT;
44  
45      /** The current logging level. */
46      private static Level defaultLevel = Level.INFO;
47  
48      /** The writers registered with this CategoryLogger. */
49      private static Set<Writer> writers = new HashSet<>();
50  
51      /** The log level per Writer. */
52      private static Map<Writer, Level> writerLevels = new HashMap<>();
53  
54      /** The message format per Writer. */
55      private static Map<Writer, String> writerFormats = new HashMap<>();
56  
57      /** The categories to log. */
58      protected static Set<LogCategory> categories = new HashSet<>(256);
59  
60      /** The console writer, replacing the default one. */
61      private static Writer consoleWriter;
62  
63      /** The delegate logger instance that does the actual logging work, after a positive filter outcome. */
64      protected static DelegateLogger delegateLogger = new DelegateLogger(true);
65  
66      /** The delegate logger that returns immediately after a negative filter outcome. */
67      protected static DelegateLogger noLogger = new DelegateLogger(false);
68  
69      /** */
70      protected CategoryLogger()
71      {
72          // Utility class.
73      }
74  
75      static
76      {
77          create();
78      }
79  
80      /**
81       * Create a new logger for the system console. Note that this REPLACES current writers. Note that the initial LogCategory is
82       * LogCategory.ALL, so all categories will be logged. This category has to be explicitly removed (or new categories have to
83       * be set) to log a limited set of categories.
84       */
85      protected static void create()
86      {
87          consoleWriter = new ConsoleWriter();
88          writers.add(consoleWriter);
89          Configurator.currentConfig().writer(consoleWriter, defaultLevel, defaultMessageFormat).activate();
90          categories.add(LogCategory.ALL);
91      }
92  
93      /**
94       * Set a new logging format for the message lines of all writers. The default message format is:<br>
95       * {class_name}.{method}:{line} {message|indent=4}<br>
96       * <br>
97       * A few popular placeholders that can be used:<br>
98       * - {class} Fully-qualified class name where the logging request is issued<br>
99       * - {class_name} Class name (without package) where the logging request is issued<br>
100      * - {date} Date and time of the logging request, e.g. {date:yyyy-MM-dd HH:mm:ss} [SimpleDateFormat]<br>
101      * - {level} Logging level of the created log entry<br>
102      * - {line} Line number from where the logging request is issued<br>
103      * - {message} Associated message of the created log entry<br>
104      * - {method} Method name from where the logging request is issued<br>
105      * - {package} Package where the logging request is issued<br>
106      * @see <a href="https://tinylog.org/configuration#format">https://tinylog.org/configuration</a>
107      * @param newMessageFormat the new formatting pattern to use for all registered writers
108      */
109     public static void setAllLogMessageFormat(final String newMessageFormat)
110     {
111         for (Writer writer : writers)
112         {
113             Configurator.currentConfig().removeWriter(writer).activate();
114             defaultMessageFormat = newMessageFormat;
115             writerFormats.put(writer, newMessageFormat);
116             Configurator.currentConfig().addWriter(writer, defaultLevel, defaultMessageFormat).activate();
117         }
118     }
119 
120     /**
121      * Set a new logging level for all registered writers.
122      * @param newLevel the new log level for all registered writers
123      */
124     public static void setAllLogLevel(final Level newLevel)
125     {
126         for (Writer writer : writers)
127         {
128             Configurator.currentConfig().removeWriter(writer).activate();
129             defaultLevel = newLevel;
130             writerLevels.put(writer, newLevel);
131             Configurator.currentConfig().addWriter(writer, defaultLevel, defaultMessageFormat).activate();
132         }
133     }
134 
135     /**
136      * Set a new logging format for the message lines of a writer. The default message format is:<br>
137      * {class_name}.{method}:{line} {message|indent=4}<br>
138      * <br>
139      * A few popular placeholders that can be used:<br>
140      * - {class} Fully-qualified class name where the logging request is issued<br>
141      * - {class_name} Class name (without package) where the logging request is issued<br>
142      * - {date} Date and time of the logging request, e.g. {date:yyyy-MM-dd HH:mm:ss} [SimpleDateFormat]<br>
143      * - {level} Logging level of the created log entry<br>
144      * - {line} Line number from where the logging request is issued<br>
145      * - {message} Associated message of the created log entry<br>
146      * - {method} Method name from where the logging request is issued<br>
147      * - {package} Package where the logging request is issued<br>
148      * @see <a href="https://tinylog.org/configuration#format">https://tinylog.org/configuration</a>
149      * @param writer the writer to change the message format for
150      * @param newMessageFormat the new formatting pattern to use for all registered writers
151      */
152     public static void setLogMessageFormat(final Writer writer, final String newMessageFormat)
153     {
154         Configurator.currentConfig().removeWriter(writer).activate();
155         writerFormats.put(writer, newMessageFormat);
156         Configurator.currentConfig().addWriter(writer, writerLevels.get(writer), newMessageFormat).activate();
157     }
158 
159     /**
160      * Set a new logging level for one of the registered writers.
161      * @param writer the writer to change the log level for
162      * @param newLevel the new log level for the writer
163      */
164     public static void setLogLevel(final Writer writer, final Level newLevel)
165     {
166         Configurator.currentConfig().removeWriter(writer).activate();
167         writerLevels.put(writer, newLevel);
168         Configurator.currentConfig().addWriter(writer, newLevel, writerFormats.get(writer)).activate();
169     }
170 
171     /**
172      * Add a category to be logged to the Writers.
173      * @param logCategory the LogCategory to add
174      */
175     public static void addLogCategory(final LogCategory logCategory)
176     {
177         categories.add(logCategory);
178     }
179 
180     /**
181      * Remove a category to be logged to the Writers.
182      * @param logCategory the LogCategory to remove
183      */
184     public static void removeLogCategory(final LogCategory logCategory)
185     {
186         categories.remove(logCategory);
187     }
188 
189     /**
190      * Set the categories to be logged to the Writers.
191      * @param newLogCategories the LogCategories to set, replacing the previous ones
192      */
193     public static void setLogCategories(final LogCategory... newLogCategories)
194     {
195         categories.clear();
196         categories.addAll(Arrays.asList(newLogCategories));
197     }
198 
199     /* ****************************************** FILTER ******************************************/
200 
201     /**
202      * The "pass" filter that will result in always trying to log.
203      * @return the logger that tries to execute logging (delegateLogger)
204      */
205     public static DelegateLogger always()
206     {
207         return delegateLogger;
208     }
209 
210     /**
211      * Check whether the provided category needs to be logged. Note that when LogCategory.ALL is contained in the categories,
212      * filter will return true.
213      * @param logCategory the category to check for.
214      * @return the logger that either tries to log (delegateLogger), or returns without logging (noLogger)
215      */
216     public static DelegateLogger filter(final LogCategory logCategory)
217     {
218         if (categories.contains(LogCategory.ALL))
219             return delegateLogger;
220         if (categories.contains(logCategory))
221             return delegateLogger;
222         return noLogger;
223     }
224 
225     /**
226      * Check whether the provided categories contain one or more categories that need to be logged. Note that when
227      * LogCategory.ALL is contained in the categories, filter will return true.
228      * @param logCategories LogCategory...; elements or array with the categories to check for
229      * @return the logger that either tries to log (delegateLogger), or returns without logging (noLogger)
230      */
231     public static DelegateLogger filter(final LogCategory... logCategories)
232     {
233         if (categories.contains(LogCategory.ALL))
234             return delegateLogger;
235         for (LogCategory logCategory : logCategories)
236         {
237             if (categories.contains(logCategory))
238                 return delegateLogger;
239         }
240         return noLogger;
241     }
242 
243     /**
244      * Check whether the provided categories contain one or more categories that need to be logged. Note that when
245      * LogCategory.ALL is contained in the categories, filter will return true.
246      * @param logCategories Set&lt;LogCategory&gt;; the categories to check for
247      * @return the logger that either tries to log (delegateLogger), or returns without logging (noLogger)
248      */
249     public static DelegateLogger filter(final Set<LogCategory> logCategories)
250     {
251         if (categories.contains(LogCategory.ALL))
252             return delegateLogger;
253         for (LogCategory logCategory : logCategories)
254         {
255             if (categories.contains(logCategory))
256                 return delegateLogger;
257         }
258         return noLogger;
259     }
260 
261     /**
262      * DelegateLogger class that takes care of actually logging the message and/or exception. <br>
263      * <br>
264      * Copyright (c) 2003-2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
265      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
266      * The source code and binary code of this software is proprietary information of Delft University of Technology.
267      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
268      */
269     public static class DelegateLogger
270     {
271         /** Should we try to log or not? */
272         private final boolean log;
273 
274         /**
275          * @param log indicate whether we should log or not.
276          */
277         public DelegateLogger(final boolean log)
278         {
279             super();
280             this.log = log;
281         }
282 
283         /* ****************************************** TRACE ******************************************/
284 
285         /**
286          * Create a trace log entry that will always be output, independent of LogCategory settings.
287          * @param object Object; the result of the <code>toString()</code> method of <code>object</code> will be logged
288          */
289         public void trace(final Object object)
290         {
291             if (this.log)
292                 LogEntryForwarder.forward(1, Level.TRACE, object);
293         }
294 
295         /**
296          * Create a trace log entry that will always be output, independent of LogCategory settings.
297          * @param message String; the message to log
298          */
299         public void trace(final String message)
300         {
301             if (this.log)
302                 LogEntryForwarder.forward(1, Level.TRACE, message);
303         }
304 
305         /**
306          * Create a trace log entry that will always be output, independent of LogCategory settings.
307          * @param message String; the message to be logged, where {} entries will be replaced by arguments
308          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
309          */
310         public void trace(final String message, final Object... arguments)
311         {
312             if (this.log)
313                 LogEntryForwarder.forward(1, Level.TRACE, message, arguments);
314         }
315 
316         /**
317          * Create a trace log entry that will always be output, independent of LogCategory settings.
318          * @param exception Throwable; the exception to log
319          */
320         public void trace(final Throwable exception)
321         {
322             if (this.log)
323                 LogEntryForwarder.forward(1, Level.TRACE, exception);
324         }
325 
326         /**
327          * Create a trace log entry that will always be output, independent of LogCategory settings.
328          * @param exception Throwable; the exception to log
329          * @param message String; the message to log
330          */
331         public void trace(final Throwable exception, final String message)
332         {
333             if (this.log)
334                 LogEntryForwarder.forward(1, Level.TRACE, exception, message);
335         }
336 
337         /**
338          * Create a trace log entry that will always be output, independent of LogCategory settings.
339          * @param exception Throwable; the exception to log
340          * @param message String; the message to log, where {} entries will be replaced by arguments
341          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
342          */
343         public void trace(final Throwable exception, final String message, final Object... arguments)
344         {
345             if (this.log)
346                 LogEntryForwarder.forward(1, Level.TRACE, exception, message, arguments);
347         }
348 
349         /* ****************************************** DEBUG ******************************************/
350 
351         /**
352          * Create a debug log entry that will always be output, independent of LogCategory settings.
353          * @param object Object; the result of the <code>toString()</code> method of <code>object</code> will be logged
354          */
355         public void debug(final Object object)
356         {
357             if (this.log)
358                 LogEntryForwarder.forward(1, Level.DEBUG, object);
359         }
360 
361         /**
362          * Create a debug log entry that will always be output, independent of LogCategory settings.
363          * @param message String; the message to log
364          */
365         public void debug(final String message)
366         {
367             if (this.log)
368                 LogEntryForwarder.forward(1, Level.DEBUG, message);
369         }
370 
371         /**
372          * Create a debug log entry that will always be output, independent of LogCategory settings.
373          * @param message String; the message to be logged, where {} entries will be replaced by arguments
374          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
375          */
376         public void debug(final String message, final Object... arguments)
377         {
378             if (this.log)
379                 LogEntryForwarder.forward(1, Level.DEBUG, message, arguments);
380         }
381 
382         /**
383          * Create a debug log entry that will always be output, independent of LogCategory settings.
384          * @param exception Throwable; the exception to log
385          */
386         public void debug(final Throwable exception)
387         {
388             if (this.log)
389                 LogEntryForwarder.forward(1, Level.DEBUG, exception);
390         }
391 
392         /**
393          * Create a debug log entry that will always be output, independent of LogCategory settings.
394          * @param exception Throwable; the exception to log
395          * @param message String; the message to log
396          */
397         public void debug(final Throwable exception, final String message)
398         {
399             if (this.log)
400                 LogEntryForwarder.forward(1, Level.DEBUG, exception, message);
401         }
402 
403         /**
404          * Create a debug log entry that will always be output, independent of LogCategory settings.
405          * @param exception Throwable; the exception to log
406          * @param message String; the message to log, where {} entries will be replaced by arguments
407          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
408          */
409         public void debug(final Throwable exception, final String message, final Object... arguments)
410         {
411             if (this.log)
412                 LogEntryForwarder.forward(1, Level.DEBUG, exception, message, arguments);
413         }
414 
415         /* ****************************************** INFO ******************************************/
416 
417         /**
418          * Create a info log entry that will always be output, independent of LogCategory settings.
419          * @param object Object; the result of the <code>toString()</code> method of <code>object</code> will be logged
420          */
421         public void info(final Object object)
422         {
423             if (this.log)
424                 LogEntryForwarder.forward(1, Level.INFO, object);
425         }
426 
427         /**
428          * Create a info log entry that will always be output, independent of LogCategory settings.
429          * @param message String; the message to log
430          */
431         public void info(final String message)
432         {
433             if (this.log)
434                 LogEntryForwarder.forward(1, Level.INFO, message);
435         }
436 
437         /**
438          * Create a info log entry that will always be output, independent of LogCategory settings.
439          * @param message String; the message to be logged, where {} entries will be replaced by arguments
440          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
441          */
442         public void info(final String message, final Object... arguments)
443         {
444             if (this.log)
445                 LogEntryForwarder.forward(1, Level.INFO, message, arguments);
446         }
447 
448         /**
449          * Create a info log entry that will always be output, independent of LogCategory settings.
450          * @param exception Throwable; the exception to log
451          */
452         public void info(final Throwable exception)
453         {
454             if (this.log)
455                 LogEntryForwarder.forward(1, Level.INFO, exception);
456         }
457 
458         /**
459          * Create a info log entry that will always be output, independent of LogCategory settings.
460          * @param exception Throwable; the exception to log
461          * @param message String; the message to log
462          */
463         public void info(final Throwable exception, final String message)
464         {
465             if (this.log)
466                 LogEntryForwarder.forward(1, Level.INFO, exception, message);
467         }
468 
469         /**
470          * Create a info log entry that will always be output, independent of LogCategory settings.
471          * @param exception Throwable; the exception to log
472          * @param message String; the message to log, where {} entries will be replaced by arguments
473          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
474          */
475         public void info(final Throwable exception, final String message, final Object... arguments)
476         {
477             if (this.log)
478                 LogEntryForwarder.forward(1, Level.INFO, exception, message, arguments);
479         }
480 
481         /* ****************************************** WARN ******************************************/
482 
483         /**
484          * Create a warn log entry that will always be output, independent of LogCategory settings.
485          * @param object Object; the result of the <code>toString()</code> method of <code>object</code> will be logged
486          */
487         public void warn(final Object object)
488         {
489             if (this.log)
490                 LogEntryForwarder.forward(1, Level.WARNING, object);
491         }
492 
493         /**
494          * Create a warn log entry that will always be output, independent of LogCategory settings.
495          * @param message String; the message to log
496          */
497         public void warn(final String message)
498         {
499             if (this.log)
500                 LogEntryForwarder.forward(1, Level.WARNING, message);
501         }
502 
503         /**
504          * Create a warn log entry that will always be output, independent of LogCategory settings.
505          * @param message String; the message to be logged, where {} entries will be replaced by arguments
506          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
507          */
508         public void warn(final String message, final Object... arguments)
509         {
510             if (this.log)
511                 LogEntryForwarder.forward(1, Level.WARNING, message, arguments);
512         }
513 
514         /**
515          * Create a warn log entry that will always be output, independent of LogCategory settings.
516          * @param exception Throwable; the exception to log
517          */
518         public void warn(final Throwable exception)
519         {
520             if (this.log)
521                 LogEntryForwarder.forward(1, Level.WARNING, exception);
522         }
523 
524         /**
525          * Create a warn log entry that will always be output, independent of LogCategory settings.
526          * @param exception Throwable; the exception to log
527          * @param message String; the message to log
528          */
529         public void warn(final Throwable exception, final String message)
530         {
531             if (this.log)
532                 LogEntryForwarder.forward(1, Level.WARNING, exception, message);
533         }
534 
535         /**
536          * Create a warn log entry that will always be output, independent of LogCategory settings.
537          * @param exception Throwable; the exception to log
538          * @param message String; the message to log, where {} entries will be replaced by arguments
539          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
540          */
541         public void warn(final Throwable exception, final String message, final Object... arguments)
542         {
543             if (this.log)
544                 LogEntryForwarder.forward(1, Level.WARNING, exception, message, arguments);
545         }
546 
547         /* ****************************************** ERROR ******************************************/
548 
549         /**
550          * Create a error log entry that will always be output, independent of LogCategory settings.
551          * @param object Object; the result of the <code>toString()</code> method of <code>object</code> will be logged
552          */
553         public void error(final Object object)
554         {
555             if (this.log)
556                 LogEntryForwarder.forward(1, Level.ERROR, object);
557         }
558 
559         /**
560          * Create a error log entry that will always be output, independent of LogCategory settings.
561          * @param message String; the message to log
562          */
563         public void error(final String message)
564         {
565             if (this.log)
566                 LogEntryForwarder.forward(1, Level.ERROR, message);
567         }
568 
569         /**
570          * Create a error log entry that will always be output, independent of LogCategory settings.
571          * @param message String; the message to be logged, where {} entries will be replaced by arguments
572          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
573          */
574         public void error(final String message, final Object... arguments)
575         {
576             if (this.log)
577                 LogEntryForwarder.forward(1, Level.ERROR, message, arguments);
578         }
579 
580         /**
581          * Create a error log entry that will always be output, independent of LogCategory settings.
582          * @param exception Throwable; the exception to log
583          */
584         public void error(final Throwable exception)
585         {
586             if (this.log)
587                 LogEntryForwarder.forward(1, Level.ERROR, exception);
588         }
589 
590         /**
591          * Create a error log entry that will always be output, independent of LogCategory settings.
592          * @param exception Throwable; the exception to log
593          * @param message String; the message to log
594          */
595         public void error(final Throwable exception, final String message)
596         {
597             if (this.log)
598                 LogEntryForwarder.forward(1, Level.ERROR, exception, message);
599         }
600 
601         /**
602          * Create a error log entry that will always be output, independent of LogCategory settings.
603          * @param exception Throwable; the exception to log
604          * @param message String; the message to log, where {} entries will be replaced by arguments
605          * @param arguments Object...; the arguments to substitute for the {} entries in the message string
606          */
607         public void error(final Throwable exception, final String message, final Object... arguments)
608         {
609             if (this.log)
610                 LogEntryForwarder.forward(1, Level.ERROR, exception, message, arguments);
611         }
612     }
613 }