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

Last change on this file since 15590 was 15590, checked in by Don-vip, 4 years ago

fix PMD warnings forgotten in previous commit

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