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

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

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

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