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