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