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