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

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

see #16010 - use JMockit to enable more extensive test coverage (patch by ris, modified)

see https://github.com/openstreetmap/josm/pull/24/commits for details

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