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

Last change on this file since 19050 was 19050, checked in by taylor.smock, 15 months ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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