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

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

see #15709 - fix a major memory leak where the first created ListenerList were instance of ListenerList.TracingListenerList, which keeps a reference to removed listeners, due to bad logging configuration

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