001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.OutputStream; 007import java.io.PrintWriter; 008import java.io.StringWriter; 009import java.io.UnsupportedEncodingException; 010import java.text.MessageFormat; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.List; 014import java.util.function.Supplier; 015import java.util.logging.ConsoleHandler; 016import java.util.logging.Handler; 017import java.util.logging.Level; 018import java.util.logging.LogRecord; 019import java.util.logging.Logger; 020 021import org.openstreetmap.josm.tools.bugreport.BugReport; 022 023/** 024 * This class contains utility methods to log errors and warnings. 025 * <p> 026 * There are multiple log levels supported. 027 * @author Michael Zangl 028 * @since 10899 029 */ 030public final class Logging { 031 /** 032 * The josm internal log level indicating a severe error in the application that usually leads to a crash. 033 */ 034 public static final Level LEVEL_ERROR = Level.SEVERE; 035 /** 036 * The josm internal log level to use when something that may lead to a crash or wrong behaviour has happened. 037 */ 038 public static final Level LEVEL_WARN = Level.WARNING; 039 /** 040 * The josm internal log level to use for important events that will be useful when debugging problems 041 */ 042 public static final Level LEVEL_INFO = Level.INFO; 043 /** 044 * The josm internal log level to print debug output 045 */ 046 public static final Level LEVEL_DEBUG = Level.FINE; 047 /** 048 * The finest log level josm supports. This lets josm print a lot of debug output. 049 */ 050 public static final Level LEVEL_TRACE = Level.FINEST; 051 private static final Logger LOGGER = Logger.getAnonymousLogger(); 052 private static final RememberWarningHandler WARNINGS = new RememberWarningHandler(); 053 054 /** 055 * A {@link ConsoleHandler} with a couple of extra features, allowing it to be targeted at an 056 * an arbitrary {@link OutputStream} which it can be asked to reacquire the reference for on demand 057 * through {@link #reacquireOutputStream()}. It can also prevent a LogRecord's output if a 058 * specified {@code prioritizedHandler} would have outputted it. 059 * @since 14052 060 */ 061 public static class ReacquiringConsoleHandler extends ConsoleHandler { 062 private final Supplier<OutputStream> outputStreamSupplier; 063 private final Handler prioritizedHandler; 064 private OutputStream outputStreamMemo; 065 /** 066 * This variables is set to true as soon as the superconstructor has completed. 067 * The superconstructor calls {@code setOutputStream(System.err)}, any subsequent call of 068 * {@link #setOutputStream(OutputStream)} would then flush and close {@link System#err}. To avoid this, 069 * we override {@link #setOutputStream(OutputStream)} to completely ignore all calls from the superconstructor. 070 */ 071 private final boolean superCompleted; 072 073 /** 074 * Construct a new {@link ReacquiringConsoleHandler}. 075 * @param outputStreamSupplier A {@link Supplier} which will return the desired 076 * {@link OutputStream} for this handler when called. Particularly useful if you happen to be 077 * using a test framework which will switch out references to the stderr/stdout streams with 078 * new dummy ones from time to time. 079 * @param prioritizedHandler If non-null, will suppress output of any log records which pass this 080 * handler's {@code Handler#isLoggable(LogRecord)} method. 081 */ 082 public ReacquiringConsoleHandler( 083 final Supplier<OutputStream> outputStreamSupplier, 084 final Handler prioritizedHandler 085 ) { 086 super(); 087 superCompleted = true; 088 this.outputStreamSupplier = outputStreamSupplier; 089 this.prioritizedHandler = prioritizedHandler; 090 091 try { 092 // Make sure we use the correct console encoding on Windows 093 this.setEncoding(System.getProperty("sun.stdout.encoding")); 094 } catch (SecurityException | UnsupportedEncodingException e) { 095 System.err.println(e); 096 } 097 this.reacquireOutputStream(); 098 } 099 100 /** 101 * Set output stream to one acquired from calling outputStreamSupplier 102 */ 103 public synchronized void reacquireOutputStream() { 104 final OutputStream reacquiredStream = this.outputStreamSupplier.get(); // NOPMD 105 106 // only bother calling setOutputStream if it's actually different, as setOutputStream 107 // has the nasty side effect of closing any previous output stream, which is certainly not 108 // what we would want were the new stream the same one 109 if (reacquiredStream != this.outputStreamMemo) { 110 this.setOutputStream(reacquiredStream); 111 } 112 } 113 114 @Override 115 public synchronized void setOutputStream(final OutputStream outputStream) { 116 // Ignore calls from superconstructor (see javadoc of the variable for details) 117 if (superCompleted) { 118 // this wouldn't be necessary if StreamHandler made it possible to see what the current 119 // output stream is set to 120 this.outputStreamMemo = outputStream; 121 super.setOutputStream(outputStream); 122 } 123 } 124 125 @Override 126 public synchronized void publish(LogRecord record) { 127 if (this.prioritizedHandler == null || !this.prioritizedHandler.isLoggable(record)) { 128 super.publish(record); 129 } 130 } 131 } 132 133 static { 134 // We need to be sure java.locale.providers system property is initialized by JOSM, not by JRE 135 // The call to ConsoleHandler constructor makes the JRE access this property by side effect 136 I18n.setupJavaLocaleProviders(); 137 138 LOGGER.setLevel(Level.ALL); 139 LOGGER.setUseParentHandlers(false); 140 141 // for a more concise logging output via java.util.logging.SimpleFormatter 142 Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n"); 143 144 ConsoleHandler stderr = new ReacquiringConsoleHandler(() -> System.err, null); 145 LOGGER.addHandler(stderr); 146 try { 147 stderr.setLevel(LEVEL_WARN); 148 } catch (SecurityException e) { 149 System.err.println("Unable to set logging level: " + e.getMessage()); 150 } 151 152 ConsoleHandler stdout = new ReacquiringConsoleHandler(() -> System.out, stderr); 153 LOGGER.addHandler(stdout); 154 try { 155 stdout.setLevel(Level.ALL); 156 } catch (SecurityException e) { 157 System.err.println("Unable to set logging level: " + e.getMessage()); 158 } 159 160 LOGGER.addHandler(WARNINGS); 161 // Set log level to info, otherwise the first ListenerList created will be for debugging purposes and create memory leaks 162 Logging.setLogLevel(Logging.LEVEL_INFO); 163 } 164 165 private Logging() { 166 // hide 167 } 168 169 /** 170 * Set the global log level. 171 * @param level The log level to use 172 */ 173 public static void setLogLevel(Level level) { 174 LOGGER.setLevel(level); 175 } 176 177 /** 178 * Prints an error message if logging is on. 179 * @param message The message to print. 180 */ 181 public static void error(String message) { 182 logPrivate(LEVEL_ERROR, message); 183 } 184 185 /** 186 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format} 187 * function to format text. 188 * @param pattern The formatted message to print. 189 * @param args The objects to insert into format string. 190 */ 191 public static void error(String pattern, Object... args) { 192 logPrivate(LEVEL_ERROR, pattern, args); 193 } 194 195 /** 196 * Prints an error message for the given Throwable if logging is on. 197 * @param t The throwable object causing the error. 198 * @since 12620 199 */ 200 public static void error(Throwable t) { 201 logWithStackTrace(Logging.LEVEL_ERROR, t); 202 } 203 204 /** 205 * Prints a warning message if logging is on. 206 * @param message The message to print. 207 */ 208 public static void warn(String message) { 209 logPrivate(LEVEL_WARN, message); 210 } 211 212 /** 213 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format} 214 * function to format text. 215 * @param pattern The formatted message to print. 216 * @param args The objects to insert into format string. 217 */ 218 public static void warn(String pattern, Object... args) { 219 logPrivate(LEVEL_WARN, pattern, args); 220 } 221 222 /** 223 * Prints a warning message for the given Throwable if logging is on. 224 * @param t The throwable object causing the error. 225 * @since 12620 226 */ 227 public static void warn(Throwable t) { 228 logWithStackTrace(Logging.LEVEL_WARN, t); 229 } 230 231 /** 232 * Prints a info message if logging is on. 233 * @param message The message to print. 234 */ 235 public static void info(String message) { 236 logPrivate(LEVEL_INFO, message); 237 } 238 239 /** 240 * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format} 241 * function to format text. 242 * @param pattern The formatted message to print. 243 * @param args The objects to insert into format string. 244 */ 245 public static void info(String pattern, Object... args) { 246 logPrivate(LEVEL_INFO, pattern, args); 247 } 248 249 /** 250 * Prints a info message for the given Throwable if logging is on. 251 * @param t The throwable object causing the error. 252 * @since 12620 253 */ 254 public static void info(Throwable t) { 255 logWithStackTrace(Logging.LEVEL_INFO, t); 256 } 257 258 /** 259 * Prints a debug message if logging is on. 260 * @param message The message to print. 261 */ 262 public static void debug(String message) { 263 logPrivate(LEVEL_DEBUG, message); 264 } 265 266 /** 267 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format} 268 * function to format text. 269 * @param pattern The formatted message to print. 270 * @param args The objects to insert into format string. 271 */ 272 public static void debug(String pattern, Object... args) { 273 logPrivate(LEVEL_DEBUG, pattern, args); 274 } 275 276 /** 277 * Prints a debug message for the given Throwable if logging is on. 278 * @param t The throwable object causing the error. 279 * @since 12620 280 */ 281 public static void debug(Throwable t) { 282 log(Logging.LEVEL_DEBUG, t); 283 } 284 285 /** 286 * Prints a trace message if logging is on. 287 * @param message The message to print. 288 */ 289 public static void trace(String message) { 290 logPrivate(LEVEL_TRACE, message); 291 } 292 293 /** 294 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format} 295 * function to format text. 296 * @param pattern The formatted message to print. 297 * @param args The objects to insert into format string. 298 */ 299 public static void trace(String pattern, Object... args) { 300 logPrivate(LEVEL_TRACE, pattern, args); 301 } 302 303 /** 304 * Prints a trace message for the given Throwable if logging is on. 305 * @param t The throwable object causing the error. 306 * @since 12620 307 */ 308 public static void trace(Throwable t) { 309 log(Logging.LEVEL_TRACE, t); 310 } 311 312 /** 313 * Logs a throwable that happened. The stack trace is not added to the log. 314 * @param level The level. 315 * @param t The throwable that should be logged. 316 * @see #logWithStackTrace(Level, Throwable) 317 */ 318 public static void log(Level level, Throwable t) { 319 logPrivate(level, () -> getErrorLog(null, t)); 320 } 321 322 /** 323 * Logs a throwable that happened. The stack trace is not added to the log. 324 * @param level The level. 325 * @param message An additional error message 326 * @param t The throwable that caused the message 327 * @see #logWithStackTrace(Level, String, Throwable) 328 */ 329 public static void log(Level level, String message, Throwable t) { 330 logPrivate(level, () -> getErrorLog(message, t)); 331 } 332 333 /** 334 * Logs a throwable that happened. Adds the stack trace to the log. 335 * @param level The level. 336 * @param t The throwable that should be logged. 337 * @see #log(Level, Throwable) 338 */ 339 public static void logWithStackTrace(Level level, Throwable t) { 340 logPrivate(level, () -> getErrorLogWithStack(null, t)); 341 } 342 343 /** 344 * Logs a throwable that happened. Adds the stack trace to the log. 345 * @param level The level. 346 * @param message An additional error message 347 * @param t The throwable that should be logged. 348 * @see #logWithStackTrace(Level, Throwable) 349 */ 350 public static void logWithStackTrace(Level level, String message, Throwable t) { 351 logPrivate(level, () -> getErrorLogWithStack(message, t)); 352 } 353 354 /** 355 * Logs a throwable that happened. Adds the stack trace to the log. 356 * @param level The level. 357 * @param t The throwable that should be logged. 358 * @param pattern The formatted message to print. 359 * @param args The objects to insert into format string 360 * @see #logWithStackTrace(Level, Throwable) 361 */ 362 public static void logWithStackTrace(Level level, Throwable t, String pattern, Object... args) { 363 logPrivate(level, () -> getErrorLogWithStack(MessageFormat.format(pattern, args), t)); 364 } 365 366 private static void logPrivate(Level level, String pattern, Object... args) { 367 logPrivate(level, () -> MessageFormat.format(pattern, args)); 368 } 369 370 private static void logPrivate(Level level, String message) { 371 logPrivate(level, () -> message); 372 } 373 374 private static void logPrivate(Level level, Supplier<String> supplier) { 375 // all log methods immediately call one of the logPrivate methods. 376 if (LOGGER.isLoggable(level)) { 377 StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name)); 378 LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier); 379 } 380 } 381 382 /** 383 * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required. 384 * 385 * For formatting text, you should use the {@link #debug(String, Object...)} message 386 * @param level A level constant. You can e.g. use {@link Logging#LEVEL_ERROR} 387 * @return <code>true</code> if log level is enabled. 388 */ 389 public static boolean isLoggingEnabled(Level level) { 390 return LOGGER.isLoggable(level); 391 } 392 393 /** 394 * Determines if debug log level is enabled. 395 * Useful to avoid costly construction of debug messages when not enabled. 396 * @return {@code true} if log level is at least debug, {@code false} otherwise 397 * @since 12620 398 */ 399 public static boolean isDebugEnabled() { 400 return isLoggingEnabled(Logging.LEVEL_DEBUG); 401 } 402 403 /** 404 * Determines if trace log level is enabled. 405 * Useful to avoid costly construction of trace messages when not enabled. 406 * @return {@code true} if log level is at least trace, {@code false} otherwise 407 * @since 12620 408 */ 409 public static boolean isTraceEnabled() { 410 return isLoggingEnabled(Logging.LEVEL_TRACE); 411 } 412 413 private static String getErrorLog(String message, Throwable t) { 414 StringBuilder sb = new StringBuilder(); 415 if (message != null) { 416 sb.append(message).append(": "); 417 } 418 sb.append(getErrorMessage(t)); 419 return sb.toString(); 420 } 421 422 private static String getErrorLogWithStack(String message, Throwable t) { 423 StringWriter sb = new StringWriter(); 424 sb.append(getErrorLog(message, t)); 425 if (t != null) { 426 sb.append('\n'); 427 t.printStackTrace(new PrintWriter(sb)); 428 } 429 return sb.toString(); 430 } 431 432 /** 433 * Returns a human-readable message of error, also usable for developers. 434 * @param t The error 435 * @return The human-readable error message 436 */ 437 public static String getErrorMessage(Throwable t) { 438 if (t == null) { 439 return "(no error)"; 440 } 441 StringBuilder sb = new StringBuilder(t.getClass().getName()); 442 String msg = t.getMessage(); 443 if (msg != null) { 444 sb.append(": ").append(msg.trim()); 445 } 446 Throwable cause = t.getCause(); 447 if (cause != null && !cause.equals(t)) { 448 // this may cause infinite loops in the unlikely case that there is a loop in the causes. 449 sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause)); 450 } 451 return sb.toString(); 452 } 453 454 /** 455 * Clear the list of last warnings 456 */ 457 public static void clearLastErrorAndWarnings() { 458 WARNINGS.clear(); 459 } 460 461 /** 462 * Get the last error and warning messages in the order in which they were received. 463 * @return The last errors and warnings. 464 */ 465 public static List<String> getLastErrorAndWarnings() { 466 return WARNINGS.getMessages(); 467 } 468 469 /** 470 * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is preferred. 471 * @return The logger 472 */ 473 public static Logger getLogger() { 474 return LOGGER; 475 } 476 477 private static class RememberWarningHandler extends Handler { 478 private final String[] log = new String[10]; 479 private int messagesLogged; 480 481 synchronized void clear() { 482 messagesLogged = 0; 483 Arrays.fill(log, null); 484 } 485 486 @Override 487 public synchronized void publish(LogRecord record) { 488 // We don't use setLevel + isLoggable to work in WebStart Sandbox mode 489 if (record.getLevel().intValue() < LEVEL_WARN.intValue()) { 490 return; 491 } 492 493 String msg = getPrefix(record) + record.getMessage(); 494 495 // Only remember first line of message 496 int idx = msg.indexOf('\n'); 497 if (idx > 0) { 498 msg = msg.substring(0, idx); 499 } 500 log[messagesLogged % log.length] = msg; 501 messagesLogged++; 502 } 503 504 private static String getPrefix(LogRecord record) { 505 if (record.getLevel().equals(LEVEL_WARN)) { 506 return "W: "; 507 } else { 508 // worse than warn 509 return "E: "; 510 } 511 } 512 513 synchronized List<String> getMessages() { 514 List<String> logged = Arrays.asList(log); 515 ArrayList<String> res = new ArrayList<>(); 516 int logOffset = messagesLogged % log.length; 517 if (messagesLogged > logOffset) { 518 res.addAll(logged.subList(logOffset, log.length)); 519 } 520 res.addAll(logged.subList(0, logOffset)); 521 return res; 522 } 523 524 @Override 525 public synchronized void flush() { 526 // nothing to do 527 } 528 529 @Override 530 public void close() { 531 // nothing to do 532 } 533 } 534}