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

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

fix #18608 - use the correct console encoding on Windows (regression from r10899)

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