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        LOGGER.setLevel(Level.ALL);
055        LOGGER.setUseParentHandlers(false);
056
057        // for a more concise logging output via java.util.logging.SimpleFormatter
058        Utils.updateSystemProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT.%1$tL %4$s: %5$s%6$s%n");
059
060        ConsoleHandler stderr = new ConsoleHandler();
061        LOGGER.addHandler(stderr);
062        stderr.setLevel(LEVEL_WARN);
063
064        ConsoleHandler stdout = new ConsoleHandler() {
065            @Override
066            protected synchronized void setOutputStream(OutputStream out) {
067                // overwrite output stream.
068                super.setOutputStream(System.out);
069            }
070
071            @Override
072            public void publish(LogRecord record) {
073                if (!stderr.isLoggable(record)) {
074                    super.publish(record);
075                }
076            }
077        };
078        LOGGER.addHandler(stdout);
079        stdout.setLevel(Level.ALL);
080
081        LOGGER.addHandler(WARNINGS);
082    }
083
084    private Logging() {
085        // hide
086    }
087
088    /**
089     * Set the global log level.
090     * @param level The log level to use
091     */
092    public static void setLogLevel(Level level) {
093        LOGGER.setLevel(level);
094    }
095
096    /**
097     * Prints an error message if logging is on.
098     * @param message The message to print.
099     */
100    public static void error(String message) {
101        logPrivate(LEVEL_ERROR, message);
102    }
103
104    /**
105     * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
106     * function to format text.
107     * @param pattern The formatted message to print.
108     * @param args The objects to insert into format string.
109     */
110    public static void error(String pattern, Object... args) {
111        logPrivate(LEVEL_ERROR, pattern, args);
112    }
113
114    /**
115     * Prints a warning message if logging is on.
116     * @param message The message to print.
117     */
118    public static void warn(String message) {
119        logPrivate(LEVEL_WARN, message);
120    }
121
122    /**
123     * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
124     * function to format text.
125     * @param pattern The formatted message to print.
126     * @param args The objects to insert into format string.
127     */
128    public static void warn(String pattern, Object... args) {
129        logPrivate(LEVEL_WARN, pattern, args);
130    }
131
132    /**
133     * Prints a info message if logging is on.
134     * @param message The message to print.
135     */
136    public static void info(String message) {
137        logPrivate(LEVEL_INFO, message);
138    }
139
140    /**
141     * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format}
142     * function to format text.
143     * @param pattern The formatted message to print.
144     * @param args The objects to insert into format string.
145     */
146    public static void info(String pattern, Object... args) {
147        logPrivate(LEVEL_INFO, pattern, args);
148    }
149
150    /**
151     * Prints a debug message if logging is on.
152     * @param message The message to print.
153     */
154    public static void debug(String message) {
155        logPrivate(LEVEL_DEBUG, message);
156    }
157
158    /**
159     * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
160     * function to format text.
161     * @param pattern The formatted message to print.
162     * @param args The objects to insert into format string.
163     */
164    public static void debug(String pattern, Object... args) {
165        logPrivate(LEVEL_DEBUG, pattern, args);
166    }
167
168    /**
169     * Prints a trace message if logging is on.
170     * @param message The message to print.
171     */
172    public static void trace(String message) {
173        logPrivate(LEVEL_TRACE, message);
174    }
175
176    /**
177     * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
178     * function to format text.
179     * @param pattern The formatted message to print.
180     * @param args The objects to insert into format string.
181     */
182    public static void trace(String pattern, Object... args) {
183        logPrivate(LEVEL_TRACE, pattern, args);
184    }
185
186    /**
187     * Logs a throwable that happened.
188     * @param level The level.
189     * @param t The throwable that should be logged.
190     */
191    public static void log(Level level, Throwable t) {
192        logPrivate(level, () -> getErrorLog(null, t));
193    }
194
195    /**
196     * Logs a throwable that happened.
197     * @param level The level.
198     * @param message An additional error message
199     * @param t The throwable that caused the message
200     */
201    public static void log(Level level, String message, Throwable t) {
202        logPrivate(level, () -> getErrorLog(message, t));
203    }
204
205    /**
206     * Logs a throwable that happened. Adds the stack trace to the log.
207     * @param level The level.
208     * @param t The throwable that should be logged.
209     */
210    public static void logWithStackTrace(Level level, Throwable t) {
211        logPrivate(level, () -> getErrorLogWithStack(null, t));
212    }
213
214    /**
215     * Logs a throwable that happened. Adds the stack trace to the log.
216     * @param level The level.
217     * @param message An additional error message
218     * @param t The throwable that should be logged.
219     */
220    public static void logWithStackTrace(Level level, String message, Throwable t) {
221        logPrivate(level, () -> getErrorLogWithStack(message, t));
222    }
223
224    private static void logPrivate(Level level, String pattern, Object... args) {
225        logPrivate(level, () -> MessageFormat.format(pattern, args));
226    }
227
228    private static void logPrivate(Level level, String message) {
229        logPrivate(level, () -> message);
230    }
231
232    private static void logPrivate(Level level, Supplier<String> supplier) {
233        // all log methods immeadiately call one of the logPrivate methods.
234        if (LOGGER.isLoggable(level)) {
235            StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name));
236            LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier);
237        }
238    }
239
240    /**
241     * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required.
242     *
243     * For formatting text, you should use the {@link #debug(String, Object...)} message
244     * @param level A lvele constant. You can e.g. use {@link Logging#LEVEL_ERROR}
245     * @return <code>true</code> if debug is enabled.
246     */
247    public static boolean isLoggingEnabled(Level level) {
248        return LOGGER.isLoggable(level);
249    }
250
251    private static String getErrorLog(String message, Throwable t) {
252        StringBuilder sb = new StringBuilder();
253        if (message != null) {
254            sb.append(message).append(": ");
255        }
256        sb.append(getErrorMessage(t));
257        return sb.toString();
258    }
259
260    private static String getErrorLogWithStack(String message, Throwable t) {
261        StringWriter sb = new StringWriter();
262        sb.append(getErrorLog(message, t));
263        if (t != null) {
264            sb.append('\n');
265            t.printStackTrace(new PrintWriter(sb));
266        }
267        return sb.toString();
268    }
269
270    /**
271     * Returns a human-readable message of error, also usable for developers.
272     * @param t The error
273     * @return The human-readable error message
274     */
275    public static String getErrorMessage(Throwable t) {
276        if (t == null) {
277            return "(no error)";
278        }
279        StringBuilder sb = new StringBuilder(t.getClass().getName());
280        String msg = t.getMessage();
281        if (msg != null) {
282            sb.append(": ").append(msg.trim());
283        }
284        Throwable cause = t.getCause();
285        if (cause != null && !cause.equals(t)) {
286            // this may cause infinite loops in the unlikely case that there is a loop in the causes.
287            sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
288        }
289        return sb.toString();
290    }
291
292    /**
293     * Clear the list of last warnings
294     */
295    public static void clearLastErrorAndWarnings() {
296        WARNINGS.clear();
297    }
298
299    /**
300     * Get the last error and warning messages in the order in which they were received.
301     * @return The last errors and warnings.
302     */
303    public static List<String> getLastErrorAndWarnings() {
304        return WARNINGS.getMessages();
305    }
306
307    /**
308     * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is prefered.
309     * @return The logger
310     */
311    public static Logger getLogger() {
312        return LOGGER;
313    }
314
315    private static class RememberWarningHandler extends Handler {
316        private final String[] log = new String[10];
317        private int messagesLogged;
318
319        RememberWarningHandler() {
320            setLevel(LEVEL_WARN);
321        }
322
323        synchronized void clear() {
324            messagesLogged = 0;
325            Arrays.fill(log, null);
326        }
327
328        @Override
329        public synchronized void publish(LogRecord record) {
330            if (!isLoggable(record)) {
331                return;
332            }
333
334            String msg = getPrefix(record) + record.getMessage();
335
336            // Only remember first line of message
337            int idx = msg.indexOf('\n');
338            if (idx > 0) {
339                msg = msg.substring(0, idx);
340            }
341            log[messagesLogged % log.length] = msg;
342            messagesLogged++;
343        }
344
345        private static String getPrefix(LogRecord record) {
346            if (record.getLevel().equals(LEVEL_WARN)) {
347                return "W: ";
348            } else {
349                // worse than warn
350                return "E: ";
351            }
352        }
353
354        synchronized List<String> getMessages() {
355            List<String> logged = Arrays.asList(log);
356            ArrayList<String> res = new ArrayList<>();
357            int logOffset = messagesLogged % log.length;
358            if (messagesLogged > logOffset) {
359                res.addAll(logged.subList(logOffset, log.length));
360            }
361            res.addAll(logged.subList(0, logOffset));
362            return res;
363        }
364
365        @Override
366        public synchronized void flush() {
367            // nothing to do
368        }
369
370        @Override
371        public void close() {
372            // nothing to do
373        }
374    }
375}