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