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<LogCategory>; 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 }