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

Last change on this file since 17732 was 17660, checked in by Don-vip, 3 years ago

see #18737 - add JNLP robustness in order to be able to start unsigned JAR with OpenWebStart

  • Property svn:eol-style set to native
File size: 19.1 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 try {
123 super.setOutputStream(outputStream);
124 } catch (SecurityException e) {
125 System.err.println("Unable to set logging output stream: " + e.getMessage());
126 }
127 }
128 }
129
130 @Override
131 public synchronized void publish(LogRecord record) {
132 if (this.prioritizedHandler == null || !this.prioritizedHandler.isLoggable(record)) {
133 super.publish(record);
134 }
135 }
136 }
137
138 static {
139 // We need to be sure java.locale.providers system property is initialized by JOSM, not by JRE
140 // The call to ConsoleHandler constructor makes the JRE access this property by side effect
141 I18n.setupJavaLocaleProviders();
142
143 LOGGER.setLevel(Level.ALL);
144 LOGGER.setUseParentHandlers(false);
145
146 // for a more concise logging output via java.util.logging.SimpleFormatter
147 Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n");
148
149 ConsoleHandler stderr = new ReacquiringConsoleHandler(() -> System.err, null);
150 LOGGER.addHandler(stderr);
151 try {
152 stderr.setLevel(LEVEL_WARN);
153 } catch (SecurityException e) {
154 System.err.println("Unable to set logging level: " + e.getMessage());
155 }
156
157 ConsoleHandler stdout = new ReacquiringConsoleHandler(() -> System.out, stderr);
158 LOGGER.addHandler(stdout);
159 try {
160 stdout.setLevel(Level.ALL);
161 } catch (SecurityException e) {
162 System.err.println("Unable to set logging level: " + e.getMessage());
163 }
164
165 LOGGER.addHandler(WARNINGS);
166 // Set log level to info, otherwise the first ListenerList created will be for debugging purposes and create memory leaks
167 Logging.setLogLevel(Logging.LEVEL_INFO);
168 }
169
170 private Logging() {
171 // hide
172 }
173
174 /**
175 * Set the global log level.
176 * @param level The log level to use
177 */
178 public static void setLogLevel(Level level) {
179 LOGGER.setLevel(level);
180 }
181
182 /**
183 * Prints an error message if logging is on.
184 * @param message The message to print.
185 */
186 public static void error(String message) {
187 logPrivate(LEVEL_ERROR, message);
188 }
189
190 /**
191 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
192 * function to format text.
193 * @param pattern The formatted message to print.
194 * @param args The objects to insert into format string.
195 */
196 public static void error(String pattern, Object... args) {
197 logPrivate(LEVEL_ERROR, pattern, args);
198 }
199
200 /**
201 * Prints an error message for the given Throwable if logging is on.
202 * @param t The throwable object causing the error.
203 * @since 12620
204 */
205 public static void error(Throwable t) {
206 logWithStackTrace(Logging.LEVEL_ERROR, t);
207 }
208
209 /**
210 * Prints a warning message if logging is on.
211 * @param message The message to print.
212 */
213 public static void warn(String message) {
214 logPrivate(LEVEL_WARN, message);
215 }
216
217 /**
218 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
219 * function to format text.
220 * @param pattern The formatted message to print.
221 * @param args The objects to insert into format string.
222 */
223 public static void warn(String pattern, Object... args) {
224 logPrivate(LEVEL_WARN, pattern, args);
225 }
226
227 /**
228 * Prints a warning message for the given Throwable if logging is on.
229 * @param t The throwable object causing the error.
230 * @since 12620
231 */
232 public static void warn(Throwable t) {
233 logWithStackTrace(Logging.LEVEL_WARN, t);
234 }
235
236 /**
237 * Prints a info message if logging is on.
238 * @param message The message to print.
239 */
240 public static void info(String message) {
241 logPrivate(LEVEL_INFO, message);
242 }
243
244 /**
245 * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format}
246 * function to format text.
247 * @param pattern The formatted message to print.
248 * @param args The objects to insert into format string.
249 */
250 public static void info(String pattern, Object... args) {
251 logPrivate(LEVEL_INFO, pattern, args);
252 }
253
254 /**
255 * Prints a info message for the given Throwable if logging is on.
256 * @param t The throwable object causing the error.
257 * @since 12620
258 */
259 public static void info(Throwable t) {
260 logWithStackTrace(Logging.LEVEL_INFO, t);
261 }
262
263 /**
264 * Prints a debug message if logging is on.
265 * @param message The message to print.
266 */
267 public static void debug(String message) {
268 logPrivate(LEVEL_DEBUG, message);
269 }
270
271 /**
272 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
273 * function to format text.
274 * @param pattern The formatted message to print.
275 * @param args The objects to insert into format string.
276 */
277 public static void debug(String pattern, Object... args) {
278 logPrivate(LEVEL_DEBUG, pattern, args);
279 }
280
281 /**
282 * Prints a debug message for the given Throwable if logging is on.
283 * @param t The throwable object causing the error.
284 * @since 12620
285 */
286 public static void debug(Throwable t) {
287 log(Logging.LEVEL_DEBUG, t);
288 }
289
290 /**
291 * Prints a trace message if logging is on.
292 * @param message The message to print.
293 */
294 public static void trace(String message) {
295 logPrivate(LEVEL_TRACE, message);
296 }
297
298 /**
299 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
300 * function to format text.
301 * @param pattern The formatted message to print.
302 * @param args The objects to insert into format string.
303 */
304 public static void trace(String pattern, Object... args) {
305 logPrivate(LEVEL_TRACE, pattern, args);
306 }
307
308 /**
309 * Prints a trace message for the given Throwable if logging is on.
310 * @param t The throwable object causing the error.
311 * @since 12620
312 */
313 public static void trace(Throwable t) {
314 log(Logging.LEVEL_TRACE, t);
315 }
316
317 /**
318 * Logs a throwable that happened. The stack trace is not added to the log.
319 * @param level The level.
320 * @param t The throwable that should be logged.
321 * @see #logWithStackTrace(Level, Throwable)
322 */
323 public static void log(Level level, Throwable t) {
324 logPrivate(level, () -> getErrorLog(null, t));
325 }
326
327 /**
328 * Logs a throwable that happened. The stack trace is not added to the log.
329 * @param level The level.
330 * @param message An additional error message
331 * @param t The throwable that caused the message
332 * @see #logWithStackTrace(Level, String, Throwable)
333 */
334 public static void log(Level level, String message, Throwable t) {
335 logPrivate(level, () -> getErrorLog(message, t));
336 }
337
338 /**
339 * Logs a throwable that happened. Adds the stack trace to the log.
340 * @param level The level.
341 * @param t The throwable that should be logged.
342 * @see #log(Level, Throwable)
343 */
344 public static void logWithStackTrace(Level level, Throwable t) {
345 logPrivate(level, () -> getErrorLogWithStack(null, t));
346 }
347
348 /**
349 * Logs a throwable that happened. Adds the stack trace to the log.
350 * @param level The level.
351 * @param message An additional error message
352 * @param t The throwable that should be logged.
353 * @see #logWithStackTrace(Level, Throwable)
354 */
355 public static void logWithStackTrace(Level level, String message, Throwable t) {
356 logPrivate(level, () -> getErrorLogWithStack(message, t));
357 }
358
359 /**
360 * Logs a throwable that happened. Adds the stack trace to the log.
361 * @param level The level.
362 * @param t The throwable that should be logged.
363 * @param pattern The formatted message to print.
364 * @param args The objects to insert into format string
365 * @see #logWithStackTrace(Level, Throwable)
366 */
367 public static void logWithStackTrace(Level level, Throwable t, String pattern, Object... args) {
368 logPrivate(level, () -> getErrorLogWithStack(MessageFormat.format(pattern, args), t));
369 }
370
371 private static void logPrivate(Level level, String pattern, Object... args) {
372 logPrivate(level, () -> MessageFormat.format(pattern, args));
373 }
374
375 private static void logPrivate(Level level, String message) {
376 logPrivate(level, () -> message);
377 }
378
379 private static void logPrivate(Level level, Supplier<String> supplier) {
380 // all log methods immediately call one of the logPrivate methods.
381 if (LOGGER.isLoggable(level)) {
382 StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name));
383 LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier);
384 }
385 }
386
387 /**
388 * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required.
389 *
390 * For formatting text, you should use the {@link #debug(String, Object...)} message
391 * @param level A level constant. You can e.g. use {@link Logging#LEVEL_ERROR}
392 * @return <code>true</code> if log level is enabled.
393 */
394 public static boolean isLoggingEnabled(Level level) {
395 return LOGGER.isLoggable(level);
396 }
397
398 /**
399 * Determines if debug log level is enabled.
400 * Useful to avoid costly construction of debug messages when not enabled.
401 * @return {@code true} if log level is at least debug, {@code false} otherwise
402 * @since 12620
403 */
404 public static boolean isDebugEnabled() {
405 return isLoggingEnabled(Logging.LEVEL_DEBUG);
406 }
407
408 /**
409 * Determines if trace log level is enabled.
410 * Useful to avoid costly construction of trace messages when not enabled.
411 * @return {@code true} if log level is at least trace, {@code false} otherwise
412 * @since 12620
413 */
414 public static boolean isTraceEnabled() {
415 return isLoggingEnabled(Logging.LEVEL_TRACE);
416 }
417
418 private static String getErrorLog(String message, Throwable t) {
419 StringBuilder sb = new StringBuilder();
420 if (message != null) {
421 sb.append(message).append(": ");
422 }
423 sb.append(getErrorMessage(t));
424 return sb.toString();
425 }
426
427 private static String getErrorLogWithStack(String message, Throwable t) {
428 StringWriter sb = new StringWriter();
429 sb.append(getErrorLog(message, t));
430 if (t != null) {
431 sb.append('\n');
432 t.printStackTrace(new PrintWriter(sb));
433 }
434 return sb.toString();
435 }
436
437 /**
438 * Returns a human-readable message of error, also usable for developers.
439 * @param t The error
440 * @return The human-readable error message
441 */
442 public static String getErrorMessage(Throwable t) {
443 if (t == null) {
444 return "(no error)";
445 }
446 StringBuilder sb = new StringBuilder(t.getClass().getName());
447 String msg = t.getMessage();
448 if (msg != null) {
449 sb.append(": ").append(msg.trim());
450 }
451 Throwable cause = t.getCause();
452 if (cause != null && !cause.equals(t)) {
453 // this may cause infinite loops in the unlikely case that there is a loop in the causes.
454 sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
455 }
456 return sb.toString();
457 }
458
459 /**
460 * Clear the list of last warnings
461 */
462 public static void clearLastErrorAndWarnings() {
463 WARNINGS.clear();
464 }
465
466 /**
467 * Get the last error and warning messages in the order in which they were received.
468 * @return The last errors and warnings.
469 */
470 public static List<String> getLastErrorAndWarnings() {
471 return WARNINGS.getMessages();
472 }
473
474 /**
475 * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is preferred.
476 * @return The logger
477 */
478 public static Logger getLogger() {
479 return LOGGER;
480 }
481
482 private static class RememberWarningHandler extends Handler {
483 private final String[] log = new String[10];
484 private int messagesLogged;
485
486 synchronized void clear() {
487 messagesLogged = 0;
488 Arrays.fill(log, null);
489 }
490
491 @Override
492 public synchronized void publish(LogRecord record) {
493 // We don't use setLevel + isLoggable to work in WebStart Sandbox mode
494 if (record.getLevel().intValue() < LEVEL_WARN.intValue()) {
495 return;
496 }
497
498 String msg = String.format("%09.3f %s%s", startup.elapsed() / 1000., getPrefix(record), record.getMessage());
499
500 // Only remember first line of message
501 int idx = msg.indexOf('\n');
502 if (idx > 0) {
503 msg = msg.substring(0, idx);
504 }
505 log[messagesLogged % log.length] = msg;
506 messagesLogged++;
507 }
508
509 private static String getPrefix(LogRecord record) {
510 if (record.getLevel().equals(LEVEL_WARN)) {
511 return "W: ";
512 } else {
513 // worse than warn
514 return "E: ";
515 }
516 }
517
518 synchronized List<String> getMessages() {
519 List<String> logged = Arrays.asList(log);
520 ArrayList<String> res = new ArrayList<>();
521 int logOffset = messagesLogged % log.length;
522 if (messagesLogged > logOffset) {
523 res.addAll(logged.subList(logOffset, log.length));
524 }
525 res.addAll(logged.subList(0, logOffset));
526 return res;
527 }
528
529 @Override
530 public synchronized void flush() {
531 // nothing to do
532 }
533
534 @Override
535 public void close() {
536 // nothing to do
537 }
538 }
539}
Note: See TracBrowser for help on using the repository browser.