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 LOGGER.setLevel(Level.ALL); 055 LOGGER.setUseParentHandlers(false); 056 057 // for a more concise logging output via java.util.logging.SimpleFormatter 058 Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n"); 059 060 ConsoleHandler stderr = new ConsoleHandler(); 061 LOGGER.addHandler(stderr); 062 stderr.setLevel(LEVEL_WARN); 063 064 ConsoleHandler stdout = new ConsoleHandler() { 065 @Override 066 protected synchronized void setOutputStream(OutputStream out) { 067 // overwrite output stream. 068 super.setOutputStream(System.out); 069 } 070 071 @Override 072 public void publish(LogRecord record) { 073 if (!stderr.isLoggable(record)) { 074 super.publish(record); 075 } 076 } 077 }; 078 LOGGER.addHandler(stdout); 079 stdout.setLevel(Level.ALL); 080 081 LOGGER.addHandler(WARNINGS); 082 } 083 084 private Logging() { 085 // hide 086 } 087 088 /** 089 * Set the global log level. 090 * @param level The log level to use 091 */ 092 public static void setLogLevel(Level level) { 093 LOGGER.setLevel(level); 094 } 095 096 /** 097 * Prints an error message if logging is on. 098 * @param message The message to print. 099 */ 100 public static void error(String message) { 101 logPrivate(LEVEL_ERROR, message); 102 } 103 104 /** 105 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format} 106 * function to format text. 107 * @param pattern The formatted message to print. 108 * @param args The objects to insert into format string. 109 */ 110 public static void error(String pattern, Object... args) { 111 logPrivate(LEVEL_ERROR, pattern, args); 112 } 113 114 /** 115 * Prints a warning message if logging is on. 116 * @param message The message to print. 117 */ 118 public static void warn(String message) { 119 logPrivate(LEVEL_WARN, message); 120 } 121 122 /** 123 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format} 124 * function to format text. 125 * @param pattern The formatted message to print. 126 * @param args The objects to insert into format string. 127 */ 128 public static void warn(String pattern, Object... args) { 129 logPrivate(LEVEL_WARN, pattern, args); 130 } 131 132 /** 133 * Prints a info message if logging is on. 134 * @param message The message to print. 135 */ 136 public static void info(String message) { 137 logPrivate(LEVEL_INFO, message); 138 } 139 140 /** 141 * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format} 142 * function to format text. 143 * @param pattern The formatted message to print. 144 * @param args The objects to insert into format string. 145 */ 146 public static void info(String pattern, Object... args) { 147 logPrivate(LEVEL_INFO, pattern, args); 148 } 149 150 /** 151 * Prints a debug message if logging is on. 152 * @param message The message to print. 153 */ 154 public static void debug(String message) { 155 logPrivate(LEVEL_DEBUG, message); 156 } 157 158 /** 159 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format} 160 * function to format text. 161 * @param pattern The formatted message to print. 162 * @param args The objects to insert into format string. 163 */ 164 public static void debug(String pattern, Object... args) { 165 logPrivate(LEVEL_DEBUG, pattern, args); 166 } 167 168 /** 169 * Prints a trace message if logging is on. 170 * @param message The message to print. 171 */ 172 public static void trace(String message) { 173 logPrivate(LEVEL_TRACE, message); 174 } 175 176 /** 177 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format} 178 * function to format text. 179 * @param pattern The formatted message to print. 180 * @param args The objects to insert into format string. 181 */ 182 public static void trace(String pattern, Object... args) { 183 logPrivate(LEVEL_TRACE, pattern, args); 184 } 185 186 /** 187 * Logs a throwable that happened. 188 * @param level The level. 189 * @param t The throwable that should be logged. 190 */ 191 public static void log(Level level, Throwable t) { 192 logPrivate(level, () -> getErrorLog(null, t)); 193 } 194 195 /** 196 * Logs a throwable that happened. 197 * @param level The level. 198 * @param message An additional error message 199 * @param t The throwable that caused the message 200 */ 201 public static void log(Level level, String message, Throwable t) { 202 logPrivate(level, () -> getErrorLog(message, t)); 203 } 204 205 /** 206 * Logs a throwable that happened. Adds the stack trace to the log. 207 * @param level The level. 208 * @param t The throwable that should be logged. 209 */ 210 public static void logWithStackTrace(Level level, Throwable t) { 211 logPrivate(level, () -> getErrorLogWithStack(null, t)); 212 } 213 214 /** 215 * Logs a throwable that happened. Adds the stack trace to the log. 216 * @param level The level. 217 * @param message An additional error message 218 * @param t The throwable that should be logged. 219 */ 220 public static void logWithStackTrace(Level level, String message, Throwable t) { 221 logPrivate(level, () -> getErrorLogWithStack(message, t)); 222 } 223 224 private static void logPrivate(Level level, String pattern, Object... args) { 225 logPrivate(level, () -> MessageFormat.format(pattern, args)); 226 } 227 228 private static void logPrivate(Level level, String message) { 229 logPrivate(level, () -> message); 230 } 231 232 private static void logPrivate(Level level, Supplier<String> supplier) { 233 // all log methods immeadiately call one of the logPrivate methods. 234 if (LOGGER.isLoggable(level)) { 235 StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name)); 236 LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier); 237 } 238 } 239 240 /** 241 * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required. 242 * 243 * For formatting text, you should use the {@link #debug(String, Object...)} message 244 * @param level A lvele constant. You can e.g. use {@link Logging#LEVEL_ERROR} 245 * @return <code>true</code> if debug is enabled. 246 */ 247 public static boolean isLoggingEnabled(Level level) { 248 return LOGGER.isLoggable(level); 249 } 250 251 private static String getErrorLog(String message, Throwable t) { 252 StringBuilder sb = new StringBuilder(); 253 if (message != null) { 254 sb.append(message).append(": "); 255 } 256 sb.append(getErrorMessage(t)); 257 return sb.toString(); 258 } 259 260 private static String getErrorLogWithStack(String message, Throwable t) { 261 StringWriter sb = new StringWriter(); 262 sb.append(getErrorLog(message, t)); 263 if (t != null) { 264 sb.append('\n'); 265 t.printStackTrace(new PrintWriter(sb)); 266 } 267 return sb.toString(); 268 } 269 270 /** 271 * Returns a human-readable message of error, also usable for developers. 272 * @param t The error 273 * @return The human-readable error message 274 */ 275 public static String getErrorMessage(Throwable t) { 276 if (t == null) { 277 return "(no error)"; 278 } 279 StringBuilder sb = new StringBuilder(t.getClass().getName()); 280 String msg = t.getMessage(); 281 if (msg != null) { 282 sb.append(": ").append(msg.trim()); 283 } 284 Throwable cause = t.getCause(); 285 if (cause != null && !cause.equals(t)) { 286 // this may cause infinite loops in the unlikely case that there is a loop in the causes. 287 sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause)); 288 } 289 return sb.toString(); 290 } 291 292 /** 293 * Clear the list of last warnings 294 */ 295 public static void clearLastErrorAndWarnings() { 296 WARNINGS.clear(); 297 } 298 299 /** 300 * Get the last error and warning messages in the order in which they were received. 301 * @return The last errors and warnings. 302 */ 303 public static List<String> getLastErrorAndWarnings() { 304 return WARNINGS.getMessages(); 305 } 306 307 /** 308 * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is prefered. 309 * @return The logger 310 */ 311 public static Logger getLogger() { 312 return LOGGER; 313 } 314 315 private static class RememberWarningHandler extends Handler { 316 private final String[] log = new String[10]; 317 private int messagesLogged; 318 319 RememberWarningHandler() { 320 setLevel(LEVEL_WARN); 321 } 322 323 synchronized void clear() { 324 messagesLogged = 0; 325 Arrays.fill(log, null); 326 } 327 328 @Override 329 public synchronized void publish(LogRecord record) { 330 if (!isLoggable(record)) { 331 return; 332 } 333 334 String msg = getPrefix(record) + record.getMessage(); 335 336 // Only remember first line of message 337 int idx = msg.indexOf('\n'); 338 if (idx > 0) { 339 msg = msg.substring(0, idx); 340 } 341 log[messagesLogged % log.length] = msg; 342 messagesLogged++; 343 } 344 345 private static String getPrefix(LogRecord record) { 346 if (record.getLevel().equals(LEVEL_WARN)) { 347 return "W: "; 348 } else { 349 // worse than warn 350 return "E: "; 351 } 352 } 353 354 synchronized List<String> getMessages() { 355 List<String> logged = Arrays.asList(log); 356 ArrayList<String> res = new ArrayList<>(); 357 int logOffset = messagesLogged % log.length; 358 if (messagesLogged > logOffset) { 359 res.addAll(logged.subList(logOffset, log.length)); 360 } 361 res.addAll(logged.subList(0, logOffset)); 362 return res; 363 } 364 365 @Override 366 public synchronized void flush() { 367 // nothing to do 368 } 369 370 @Override 371 public void close() { 372 // nothing to do 373 } 374 } 375}