source: josm/trunk/src/org/openstreetmap/josm/tools/Logging.java@ 17335

Last change on this file since 17335 was 16905, checked in by simon04, 4 years ago

fix #19696 - Status Report: add relative timestamp to last errors/warnings

  • Property svn:eol-style set to native
File size: 18.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.OutputStream;
7import java.io.PrintWriter;
8import java.io.StringWriter;
9import java.io.UnsupportedEncodingException;
10import java.text.MessageFormat;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.List;
14import java.util.function.Supplier;
15import java.util.logging.ConsoleHandler;
16import java.util.logging.Handler;
17import java.util.logging.Level;
18import java.util.logging.LogRecord;
19import java.util.logging.Logger;
20
21import org.openstreetmap.josm.tools.bugreport.BugReport;
22
23/**
24 * This class contains utility methods to log errors and warnings.
25 * <p>
26 * There are multiple log levels supported.
27 * @author Michael Zangl
28 * @since 10899
29 */
30public final class Logging {
31 /**
32 * The josm internal log level indicating a severe error in the application that usually leads to a crash.
33 */
34 public static final Level LEVEL_ERROR = Level.SEVERE;
35 /**
36 * The josm internal log level to use when something that may lead to a crash or wrong behaviour has happened.
37 */
38 public static final Level LEVEL_WARN = Level.WARNING;
39 /**
40 * The josm internal log level to use for important events that will be useful when debugging problems
41 */
42 public static final Level LEVEL_INFO = Level.INFO;
43 /**
44 * The josm internal log level to print debug output
45 */
46 public static final Level LEVEL_DEBUG = Level.FINE;
47 /**
48 * The finest log level josm supports. This lets josm print a lot of debug output.
49 */
50 public static final Level LEVEL_TRACE = Level.FINEST;
51 private static final Logger LOGGER = Logger.getAnonymousLogger();
52 private static final RememberWarningHandler WARNINGS = new RememberWarningHandler();
53 private static final Stopwatch startup = Stopwatch.createStarted();
54
55 /**
56 * A {@link ConsoleHandler} with a couple of extra features, allowing it to be targeted at an
57 * an arbitrary {@link OutputStream} which it can be asked to reacquire the reference for on demand
58 * through {@link #reacquireOutputStream()}. It can also prevent a LogRecord's output if a
59 * specified {@code prioritizedHandler} would have outputted it.
60 * @since 14052
61 */
62 public static class ReacquiringConsoleHandler extends ConsoleHandler {
63 private final Supplier<OutputStream> outputStreamSupplier;
64 private final Handler prioritizedHandler;
65 private OutputStream outputStreamMemo;
66 /**
67 * This variables is set to true as soon as the superconstructor has completed.
68 * The superconstructor calls {@code setOutputStream(System.err)}, any subsequent call of
69 * {@link #setOutputStream(OutputStream)} would then flush and close {@link System#err}. To avoid this,
70 * we override {@link #setOutputStream(OutputStream)} to completely ignore all calls from the superconstructor.
71 */
72 private final boolean superCompleted;
73
74 /**
75 * Construct a new {@link ReacquiringConsoleHandler}.
76 * @param outputStreamSupplier A {@link Supplier} which will return the desired
77 * {@link OutputStream} for this handler when called. Particularly useful if you happen to be
78 * using a test framework which will switch out references to the stderr/stdout streams with
79 * new dummy ones from time to time.
80 * @param prioritizedHandler If non-null, will suppress output of any log records which pass this
81 * handler's {@code Handler#isLoggable(LogRecord)} method.
82 */
83 public ReacquiringConsoleHandler(
84 final Supplier<OutputStream> outputStreamSupplier,
85 final Handler prioritizedHandler
86 ) {
87 super();
88 superCompleted = true;
89 this.outputStreamSupplier = outputStreamSupplier;
90 this.prioritizedHandler = prioritizedHandler;
91
92 try {
93 // Make sure we use the correct console encoding on Windows
94 this.setEncoding(System.getProperty("sun.stdout.encoding"));
95 } catch (SecurityException | UnsupportedEncodingException e) {
96 System.err.println(e);
97 }
98 this.reacquireOutputStream();
99 }
100
101 /**
102 * Set output stream to one acquired from calling outputStreamSupplier
103 */
104 public synchronized void reacquireOutputStream() {
105 final OutputStream reacquiredStream = this.outputStreamSupplier.get(); // NOPMD
106
107 // only bother calling setOutputStream if it's actually different, as setOutputStream
108 // has the nasty side effect of closing any previous output stream, which is certainly not
109 // what we would want were the new stream the same one
110 if (reacquiredStream != this.outputStreamMemo) {
111 this.setOutputStream(reacquiredStream);
112 }
113 }
114
115 @Override
116 public synchronized void setOutputStream(final OutputStream outputStream) {
117 // Ignore calls from superconstructor (see javadoc of the variable for details)
118 if (superCompleted) {
119 // this wouldn't be necessary if StreamHandler made it possible to see what the current
120 // output stream is set to
121 this.outputStreamMemo = outputStream;
122 super.setOutputStream(outputStream);
123 }
124 }
125
126 @Override
127 public synchronized void publish(LogRecord record) {
128 if (this.prioritizedHandler == null || !this.prioritizedHandler.isLoggable(record)) {
129 super.publish(record);
130 }
131 }
132 }
133
134 static {
135 // We need to be sure java.locale.providers system property is initialized by JOSM, not by JRE
136 // The call to ConsoleHandler constructor makes the JRE access this property by side effect
137 I18n.setupJavaLocaleProviders();
138
139 LOGGER.setLevel(Level.ALL);
140 LOGGER.setUseParentHandlers(false);
141
142 // for a more concise logging output via java.util.logging.SimpleFormatter
143 Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n");
144
145 ConsoleHandler stderr = new ReacquiringConsoleHandler(() -> System.err, null);
146 LOGGER.addHandler(stderr);
147 try {
148 stderr.setLevel(LEVEL_WARN);
149 } catch (SecurityException e) {
150 System.err.println("Unable to set logging level: " + e.getMessage());
151 }
152
153 ConsoleHandler stdout = new ReacquiringConsoleHandler(() -> System.out, stderr);
154 LOGGER.addHandler(stdout);
155 try {
156 stdout.setLevel(Level.ALL);
157 } catch (SecurityException e) {
158 System.err.println("Unable to set logging level: " + e.getMessage());
159 }
160
161 LOGGER.addHandler(WARNINGS);
162 // Set log level to info, otherwise the first ListenerList created will be for debugging purposes and create memory leaks
163 Logging.setLogLevel(Logging.LEVEL_INFO);
164 }
165
166 private Logging() {
167 // hide
168 }
169
170 /**
171 * Set the global log level.
172 * @param level The log level to use
173 */
174 public static void setLogLevel(Level level) {
175 LOGGER.setLevel(level);
176 }
177
178 /**
179 * Prints an error message if logging is on.
180 * @param message The message to print.
181 */
182 public static void error(String message) {
183 logPrivate(LEVEL_ERROR, message);
184 }
185
186 /**
187 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
188 * function to format text.
189 * @param pattern The formatted message to print.
190 * @param args The objects to insert into format string.
191 */
192 public static void error(String pattern, Object... args) {
193 logPrivate(LEVEL_ERROR, pattern, args);
194 }
195
196 /**
197 * Prints an error message for the given Throwable if logging is on.
198 * @param t The throwable object causing the error.
199 * @since 12620
200 */
201 public static void error(Throwable t) {
202 logWithStackTrace(Logging.LEVEL_ERROR, t);
203 }
204
205 /**
206 * Prints a warning message if logging is on.
207 * @param message The message to print.
208 */
209 public static void warn(String message) {
210 logPrivate(LEVEL_WARN, message);
211 }
212
213 /**
214 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
215 * function to format text.
216 * @param pattern The formatted message to print.
217 * @param args The objects to insert into format string.
218 */
219 public static void warn(String pattern, Object... args) {
220 logPrivate(LEVEL_WARN, pattern, args);
221 }
222
223 /**
224 * Prints a warning message for the given Throwable if logging is on.
225 * @param t The throwable object causing the error.
226 * @since 12620
227 */
228 public static void warn(Throwable t) {
229 logWithStackTrace(Logging.LEVEL_WARN, t);
230 }
231
232 /**
233 * Prints a info message if logging is on.
234 * @param message The message to print.
235 */
236 public static void info(String message) {
237 logPrivate(LEVEL_INFO, message);
238 }
239
240 /**
241 * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format}
242 * function to format text.
243 * @param pattern The formatted message to print.
244 * @param args The objects to insert into format string.
245 */
246 public static void info(String pattern, Object... args) {
247 logPrivate(LEVEL_INFO, pattern, args);
248 }
249
250 /**
251 * Prints a info message for the given Throwable if logging is on.
252 * @param t The throwable object causing the error.
253 * @since 12620
254 */
255 public static void info(Throwable t) {
256 logWithStackTrace(Logging.LEVEL_INFO, t);
257 }
258
259 /**
260 * Prints a debug message if logging is on.
261 * @param message The message to print.
262 */
263 public static void debug(String message) {
264 logPrivate(LEVEL_DEBUG, message);
265 }
266
267 /**
268 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
269 * function to format text.
270 * @param pattern The formatted message to print.
271 * @param args The objects to insert into format string.
272 */
273 public static void debug(String pattern, Object... args) {
274 logPrivate(LEVEL_DEBUG, pattern, args);
275 }
276
277 /**
278 * Prints a debug message for the given Throwable if logging is on.
279 * @param t The throwable object causing the error.
280 * @since 12620
281 */
282 public static void debug(Throwable t) {
283 log(Logging.LEVEL_DEBUG, t);
284 }
285
286 /**
287 * Prints a trace message if logging is on.
288 * @param message The message to print.
289 */
290 public static void trace(String message) {
291 logPrivate(LEVEL_TRACE, message);
292 }
293
294 /**
295 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
296 * function to format text.
297 * @param pattern The formatted message to print.
298 * @param args The objects to insert into format string.
299 */
300 public static void trace(String pattern, Object... args) {
301 logPrivate(LEVEL_TRACE, pattern, args);
302 }
303
304 /**
305 * Prints a trace message for the given Throwable if logging is on.
306 * @param t The throwable object causing the error.
307 * @since 12620
308 */
309 public static void trace(Throwable t) {
310 log(Logging.LEVEL_TRACE, t);
311 }
312
313 /**
314 * Logs a throwable that happened. The stack trace is not added to the log.
315 * @param level The level.
316 * @param t The throwable that should be logged.
317 * @see #logWithStackTrace(Level, Throwable)
318 */
319 public static void log(Level level, Throwable t) {
320 logPrivate(level, () -> getErrorLog(null, t));
321 }
322
323 /**
324 * Logs a throwable that happened. The stack trace is not added to the log.
325 * @param level The level.
326 * @param message An additional error message
327 * @param t The throwable that caused the message
328 * @see #logWithStackTrace(Level, String, Throwable)
329 */
330 public static void log(Level level, String message, Throwable t) {
331 logPrivate(level, () -> getErrorLog(message, t));
332 }
333
334 /**
335 * Logs a throwable that happened. Adds the stack trace to the log.
336 * @param level The level.
337 * @param t The throwable that should be logged.
338 * @see #log(Level, Throwable)
339 */
340 public static void logWithStackTrace(Level level, Throwable t) {
341 logPrivate(level, () -> getErrorLogWithStack(null, t));
342 }
343
344 /**
345 * Logs a throwable that happened. Adds the stack trace to the log.
346 * @param level The level.
347 * @param message An additional error message
348 * @param t The throwable that should be logged.
349 * @see #logWithStackTrace(Level, Throwable)
350 */
351 public static void logWithStackTrace(Level level, String message, Throwable t) {
352 logPrivate(level, () -> getErrorLogWithStack(message, t));
353 }
354
355 /**
356 * Logs a throwable that happened. Adds the stack trace to the log.
357 * @param level The level.
358 * @param t The throwable that should be logged.
359 * @param pattern The formatted message to print.
360 * @param args The objects to insert into format string
361 * @see #logWithStackTrace(Level, Throwable)
362 */
363 public static void logWithStackTrace(Level level, Throwable t, String pattern, Object... args) {
364 logPrivate(level, () -> getErrorLogWithStack(MessageFormat.format(pattern, args), t));
365 }
366
367 private static void logPrivate(Level level, String pattern, Object... args) {
368 logPrivate(level, () -> MessageFormat.format(pattern, args));
369 }
370
371 private static void logPrivate(Level level, String message) {
372 logPrivate(level, () -> message);
373 }
374
375 private static void logPrivate(Level level, Supplier<String> supplier) {
376 // all log methods immediately call one of the logPrivate methods.
377 if (LOGGER.isLoggable(level)) {
378 StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name));
379 LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier);
380 }
381 }
382
383 /**
384 * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required.
385 *
386 * For formatting text, you should use the {@link #debug(String, Object...)} message
387 * @param level A level constant. You can e.g. use {@link Logging#LEVEL_ERROR}
388 * @return <code>true</code> if log level is enabled.
389 */
390 public static boolean isLoggingEnabled(Level level) {
391 return LOGGER.isLoggable(level);
392 }
393
394 /**
395 * Determines if debug log level is enabled.
396 * Useful to avoid costly construction of debug messages when not enabled.
397 * @return {@code true} if log level is at least debug, {@code false} otherwise
398 * @since 12620
399 */
400 public static boolean isDebugEnabled() {
401 return isLoggingEnabled(Logging.LEVEL_DEBUG);
402 }
403
404 /**
405 * Determines if trace log level is enabled.
406 * Useful to avoid costly construction of trace messages when not enabled.
407 * @return {@code true} if log level is at least trace, {@code false} otherwise
408 * @since 12620
409 */
410 public static boolean isTraceEnabled() {
411 return isLoggingEnabled(Logging.LEVEL_TRACE);
412 }
413
414 private static String getErrorLog(String message, Throwable t) {
415 StringBuilder sb = new StringBuilder();
416 if (message != null) {
417 sb.append(message).append(": ");
418 }
419 sb.append(getErrorMessage(t));
420 return sb.toString();
421 }
422
423 private static String getErrorLogWithStack(String message, Throwable t) {
424 StringWriter sb = new StringWriter();
425 sb.append(getErrorLog(message, t));
426 if (t != null) {
427 sb.append('\n');
428 t.printStackTrace(new PrintWriter(sb));
429 }
430 return sb.toString();
431 }
432
433 /**
434 * Returns a human-readable message of error, also usable for developers.
435 * @param t The error
436 * @return The human-readable error message
437 */
438 public static String getErrorMessage(Throwable t) {
439 if (t == null) {
440 return "(no error)";
441 }
442 StringBuilder sb = new StringBuilder(t.getClass().getName());
443 String msg = t.getMessage();
444 if (msg != null) {
445 sb.append(": ").append(msg.trim());
446 }
447 Throwable cause = t.getCause();
448 if (cause != null && !cause.equals(t)) {
449 // this may cause infinite loops in the unlikely case that there is a loop in the causes.
450 sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
451 }
452 return sb.toString();
453 }
454
455 /**
456 * Clear the list of last warnings
457 */
458 public static void clearLastErrorAndWarnings() {
459 WARNINGS.clear();
460 }
461
462 /**
463 * Get the last error and warning messages in the order in which they were received.
464 * @return The last errors and warnings.
465 */
466 public static List<String> getLastErrorAndWarnings() {
467 return WARNINGS.getMessages();
468 }
469
470 /**
471 * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is preferred.
472 * @return The logger
473 */
474 public static Logger getLogger() {
475 return LOGGER;
476 }
477
478 private static class RememberWarningHandler extends Handler {
479 private final String[] log = new String[10];
480 private int messagesLogged;
481
482 synchronized void clear() {
483 messagesLogged = 0;
484 Arrays.fill(log, null);
485 }
486
487 @Override
488 public synchronized void publish(LogRecord record) {
489 // We don't use setLevel + isLoggable to work in WebStart Sandbox mode
490 if (record.getLevel().intValue() < LEVEL_WARN.intValue()) {
491 return;
492 }
493
494 String msg = String.format("%09.3f %s%s", startup.elapsed() / 1000., getPrefix(record), record.getMessage());
495
496 // Only remember first line of message
497 int idx = msg.indexOf('\n');
498 if (idx > 0) {
499 msg = msg.substring(0, idx);
500 }
501 log[messagesLogged % log.length] = msg;
502 messagesLogged++;
503 }
504
505 private static String getPrefix(LogRecord record) {
506 if (record.getLevel().equals(LEVEL_WARN)) {
507 return "W: ";
508 } else {
509 // worse than warn
510 return "E: ";
511 }
512 }
513
514 synchronized List<String> getMessages() {
515 List<String> logged = Arrays.asList(log);
516 ArrayList<String> res = new ArrayList<>();
517 int logOffset = messagesLogged % log.length;
518 if (messagesLogged > logOffset) {
519 res.addAll(logged.subList(logOffset, log.length));
520 }
521 res.addAll(logged.subList(0, logOffset));
522 return res;
523 }
524
525 @Override
526 public synchronized void flush() {
527 // nothing to do
528 }
529
530 @Override
531 public void close() {
532 // nothing to do
533 }
534 }
535}
Note: See TracBrowser for help on using the repository browser.