Old Hat: Java Logging

Nearly all projects need to log something. You can use System.out.println(String) for that. But if you want to write the message to a file later you have to change all the logging statements in your code.

So it is recommended to use at least the Logger provided by the jdk:

java.util.logging.Logger

Other projects for logging exist e.g. the popular log4j. But this library is complex and the jar file is too big for my purposes.

Today I discovered slf4j, which is small (<25KB) and provides wrappers for the jdk-Logger and log4j. The nice thing about this library is that you can simply put the jar files into the classpath and slf4j will use e.g. the jdk-Logger. I like it more than the jdk-Logger because slf4j offers a nice interface for logging (the methods are easier to use) e.g.:

  • info(String)
  • info(String, Throwable)
  • warn(String)
  • warn(String, Throwable)
  • error(String)
  • error(String, Throwable)

But all logger don’t have a feature to log directly to the user. So I implemented a Handler that prints a JDialog with the message and the full stack trace. The stack trace will be available if the user clicks on the details button.

/*
 * This file is part of the timefinder project.
 * Visit http://www.timefinder.de for more information.
 * Copyright (C) 2008 Peter Karich.
 *
 * This project is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 2.1 of the License.
 *
 * This project is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this project; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 * or look at http://www.gnu.org
 */
package de.timefinder.framework;

import java.awt.BorderLayout;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * This class provides a Log Handler to be publish directly to the user via
 * a JDialog, which offers a details Button.
 * 
 * @author Peter Karich, peat_halatusersdotsourceforgedotnet
 */
public class SwingHandler extends Handler {

    @Override
    public void publish(LogRecord record) {
        if (!isLoggable(record)) {
            return;
        }
        int level = record.getLevel().intValue();
        int jOptLevel;

        if (level >= Level.SEVERE.intValue()) {
            jOptLevel = JOptionPane.ERROR_MESSAGE;
        } else if (level >= Level.WARNING.intValue()) {
            jOptLevel = JOptionPane.WARNING_MESSAGE;
        } else {
            jOptLevel = JOptionPane.INFORMATION_MESSAGE;
        }

        logIntoDialog(jOptLevel, record.getMessage(), record.getThrown());
    }

    @Override
    public void flush() {
    }

    @Override
    public void close() throws SecurityException {
    }

    private static void logIntoDialog(final int messageLevel,
            final String errorMessage,
            final Throwable th) {

        if (!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {

                    public void run() {
                        logIntoDialog(messageLevel, errorMessage, th);
                    }
                });
            } catch (Exception ex) {
                JOptionPane.showConfirmDialog(null, errorMessage);
            }

            return;
        }

        StringBuilder sb = new StringBuilder();
        sb.append(errorMessage);
        if (errorMessage == null || errorMessage.length() == 0) {
            if (th != null) {
                sb.append(th.getClass().getName());
                sb.append(": ");
            }
            // TODO I18N
            sb.append("Error-Message Not Available>");
        }

        String newErrMessage = sb.toString();
        if (th != null) {
            TFFormatter.printReason(sb, th);
        }

        JScrollPane scroll = new JScrollPane(new JTextArea(sb.toString(), 20, 40));
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(new JTextField(newErrMessage), BorderLayout.CENTER);
        panel.add(scroll, BorderLayout.SOUTH);
        // TODO I18N
        Object[] options = {"Okay", "Details"};
        int n = JOptionPane.NO_OPTION;
        Icon icon;

        switch (messageLevel) {
            case JOptionPane.WARNING_MESSAGE:
                icon = UIManager.getIcon("OptionPane.warningIcon");//NO I18N
                break;
            case JOptionPane.ERROR_MESSAGE:
                icon = UIManager.getIcon("OptionPane.errorIcon");//NO I18N
                break;
            case JOptionPane.INFORMATION_MESSAGE:
                icon = UIManager.getIcon("OptionPane.informationIcon");//NO I18N
                break;
            default:
                icon = UIManager.getIcon("OptionPane.questionIcon");//NO I18N
        }

        boolean visible = false;
        //Switch between Details and No-Details:
        while (n != JOptionPane.YES_OPTION) {
            scroll.setVisible(visible);
            panel.revalidate();

            n = JOptionPane.showOptionDialog(
                    null,
                    panel,
                    newErrMessage,
                    JOptionPane.YES_NO_OPTION,
                    messageLevel,
                    icon,
                    options,
                    options[0]);

            visible = !visible;
        }
    }
}