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 }