001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.OutputStream;
007import java.io.PrintWriter;
008import java.io.StringWriter;
009import java.text.MessageFormat;
010import java.util.ArrayList;
011import java.util.Arrays;
012import java.util.List;
013import java.util.function.Supplier;
014import java.util.logging.ConsoleHandler;
015import java.util.logging.Handler;
016import java.util.logging.Level;
017import java.util.logging.LogRecord;
018import java.util.logging.Logger;
019
020import org.openstreetmap.josm.tools.bugreport.BugReport;
021
022/**
023 * This class contains utility methods to log errors and warnings.
024 * <p>
025 * There are multiple log levels supported.
026 * @author Michael Zangl
027 * @since 10899
028 */
029public final class Logging {
030    /**
031     * The josm internal log level indicating a severe error in the application that usually leads to a crash.
032     */
033    public static final Level LEVEL_ERROR = Level.SEVERE;
034    /**
035     * The josm internal log level to use when something that may lead to a crash or wrong behaviour has happened.
036     */
037    public static final Level LEVEL_WARN = Level.WARNING;
038    /**
039     * The josm internal log level to use for important events that will be useful when debugging problems
040     */
041    public static final Level LEVEL_INFO = Level.INFO;
042    /**
043     * The josm internal log level to print debug output
044     */
045    public static final Level LEVEL_DEBUG = Level.FINE;
046    /**
047     * The finest log level josm supports. This lets josm print a lot of debug output.
048     */
049    public static final Level LEVEL_TRACE = Level.FINEST;
050    private static final Logger LOGGER = Logger.getAnonymousLogger();
051    private static final RememberWarningHandler WARNINGS = new RememberWarningHandler();
052
053    static {
054        // We need to be sure java.locale.providers system property is initialized by JOSM, not by JRE
055        // The call to ConsoleHandler constructor makes the JRE access this property by side effect
056        I18n.setupJavaLocaleProviders();
057
058        LOGGER.setLevel(Level.ALL);
059        LOGGER.setUseParentHandlers(false);
060
061        // for a more concise logging output via java.util.logging.SimpleFormatter
062        Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n");
063
064        ConsoleHandler stderr = new ConsoleHandler();
065        LOGGER.addHandler(stderr);
066        stderr.setLevel(LEVEL_WARN);
067
068        ConsoleHandler stdout = new ConsoleHandler() {
069            @Override
070            protected synchronized void setOutputStream(OutputStream out) {
071                // overwrite output stream.
072                super.setOutputStream(System.out);
073            }
074
075            @Override
076            public synchronized void publish(LogRecord record) {
077                if (!stderr.isLoggable(record)) {
078                    super.publish(record);
079                }
080            }
081        };
082        LOGGER.addHandler(stdout);
083        stdout.setLevel(Level.ALL);
084
085        LOGGER.addHandler(WARNINGS);
086        // Set log level to info, otherwise the first ListenerList created will be for debugging purposes and create memory leaks
087        Logging.setLogLevel(Logging.LEVEL_INFO);
088    }
089
090    private Logging() {
091        // hide
092    }
093
094    /**
095     * Set the global log level.
096     * @param level The log level to use
097     */
098    public static void setLogLevel(Level level) {
099        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}