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