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