patternform.java

// $Id: PatternFormatter.java,v 1.2 2009-06-08 17:47:18 mike Exp $

package org.faceless.util.log;

import java.text.*;
import java.util.logging.*;
import java.util.logging.Formatter;
import java.io.*;
import java.util.*;

/**
 * A simple, configurable formatter for the <code>java.util.logging</code> package.
 * It works like the
 * <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html">
 * PatternLayout</a> class for Log4J - the codes are the closest analogous matches from the
 * <code>java.util.logging</code> package. See that class for full details.
 * <dl>
 * <dd>%%</dd><dt>the percent sign</dt>
 * <dd>%c</dd><dt>the name of the logger, which unlike Log4j may be null</dt>
 * <dd>%C</dd><dt>the class name of the class that created the LogRecord, which may be null</dt>
 * <dd>%d</dd><dt>the formatted date - eg %d, %d{dd-MMM-yyyy}</dt>
 * <dd>%m</dd><dt>the formatted message included in the LogRecord, which may be localized</dt>
 * <dd>%M</dd><dt>the method name that created the LogRecord, which unlike Log4j may be null</dt>
 * <dd>%n</dd><dt>the newline character</dt>
 * <dd>%p</dd><dt>the level of the message, eg FINEST, WARNING, SEVERE</dt>
 * <dd>%t</dd><dt>the Thread ID of the thread that created the message</dt>
 * <dd>%x</dd><dt>the Throwable object included with the LogRecord: %x{notrace} will just print the exception name and message</dt>
 * <dd>%i</dd><dt>the sequence number of the LogRecord (not part of Log4j</dt>
 * <dd>%<i>n</i>P</dd><dt>the specified parameter - %1p for the first, %2p for the second and so on (not part of Log4j)</dt>
 * </dl>
 * The format is initialized using the <code>logging.properties</code> file, and may be
 * set for each level. Here's an example:
 * <pre>
 * org.faceless.util.log.PatternFormatter.format = %d{yyyy-MM-dd} %p: %m %x
 * org.faceless.util.log.PatternFormatter.SEVERE.format = %d: ERROR: %s at %c(%m)
 * </pre>
 * <p>
 * Alternatively the {@link #setFormat setFormat()} methods may be called to set the message formats
 * </p><p>
 * This code was originally written 2009-06-08 by Mike Bremford and is in the public domain.
 * </p>
 */
public class PatternFormatter extends Formatter
{
    private static final String[] LEVELS = {
        "FINEST", "FINER", "FINE", "CONFIG", "INFO", "WARNING", "SEVERE"
    };
    private static final String DEFAULT = "%d %p: %m %x";

    private Map levelformats, dateformats;
    private String format = DEFAULT;

    /**
     * Create a new PatternFormatter
     */
    public PatternFormatter() {
        LogManager manager = LogManager.getLogManager();

        this.format = DEFAULT;
        this.dateformats = Collections.synchronizedMap(new HashMap());
        String s = manager.getProperty(getClass().getName()+".format");
        if (s != null && s.trim().length() > 0) {
            setFormat(null, s);
        }
        for (int i=0;i<LEVELS.length;i++) {
            s = manager.getProperty(getClass().getName()+"."+LEVELS[i]+".format");
            if (s != null && s.trim().length() > 0) {
                setFormat(LEVELS[i], s);
            }
        }
    }

    /**
     * Set the format for the specified level, or (if <code>level</code> is null)
     * for all levels
     * @param level one of FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE or <code>null</code>
     * @param format the format string
     */
    public synchronized void setFormat(String level, String format) {
        if (level==null) {
            this.format = format;
        } else {
            if (levelformats==null) {
                levelformats = new HashMap();
            }
            levelformats.put(level, format);
        }
    }

    public String format(LogRecord record) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        StringBuffer premod = new StringBuffer();
        String thisformat = null;
        if (levelformats!=null) {
            thisformat = (String)levelformats.get(record.getLevel().getName());
        }
        if (thisformat==null) {
            thisformat = this.format;
        }

        for (int i=0;i<thisformat.length();) {
            char c = thisformat.charAt(i++);
            if (c=='\\' && i<format.length()-1) {
                c = thisformat.charAt(i++);
                switch(c) {
                  case 'r':
                    pw.write('\r');
                    break;
                  case 'n':
                    pw.write('\n');
                    break;
                  case 't':
                    pw.write('\t');
                    break;
                  case '\\':
                    pw.write('\\');
                    break;
                }
            } else if (c=='%' && i<thisformat.length()) {
                c = thisformat.charAt(i++);
                while ((c=='-' || c=='.' || Character.isDigit(c)) && i<thisformat.length()) {
                    premod.append(c);
                    c = thisformat.charAt(i++);
                }
                String postmod = null;
                if (i<thisformat.length() && thisformat.charAt(i)=='{') {
                    int start = i+1;
                    int end = start+1;
                    while (end < thisformat.length() && thisformat.charAt(end)!='}') {
                        end++;
                    }
                    if (end != thisformat.length()) {
                        postmod = thisformat.substring(start, end);
                        i = end+1;
                    }
                }

                String s;
                switch (c) {
                  case 'c':
                    s = record.getLoggerName();
                    if (s!=null && postmod!=null) {
                        try {
                            int num = Integer.parseInt(postmod);
                            int p = s.length();
                            for (int j=0;j<num && p>=0;j++) {
                                p = s.lastIndexOf(".", p-1);
                            }
                            if (p!=-1) {
                                s = s.substring(p+1);
                            }
                        } catch (Exception e) { }
                    }
                    pw.print(s);
                    break;

                  case 'd':
                    if (postmod==null) {
                        postmod = "dd-MMM-yyyy HH:mm:ss";
                    }
                    ThreadLocal tl = (ThreadLocal)dateformats.get(postmod);
                    if (tl==null) {
                        final String format = postmod;
                        tl = new ThreadLocal() {
                            protected synchronized Object initialValue() {
                                return new SimpleDateFormat(format);
                            }
                        };
                        dateformats.put(postmod, tl);
                    }
                    DateFormat df = (DateFormat)tl.get();
                    pw.print(df.format(new Date(record.getMillis())));
                    break;

                  case 'C':
                    s = record.getSourceClassName();
                    if (s!=null) {
                        if (postmod!=null) {
                            try {
                                int num = Integer.parseInt(postmod);
                                int p = s.length();
                                for (int j=0;j<num && p>=0;j++) {
                                    p = s.lastIndexOf(".", p-1);
                                }
                                if (p!=-1) {
                                    s = s.substring(p+1);
                                }
                            } catch (Exception e) { }
                        }
                        pw.print(s);
                    }
                    break;

                  case 'M':
                    if (record.getSourceMethodName()!=null) {
                        pw.print(record.getSourceMethodName());
                    }
                    break;

                  case 'm':
                    pw.print(formatMessage(record));
                    break;

                  case 'n':
                    pw.print("\n");
                    break;

                  case 'p':
                    pw.print(record.getLevel().getName());
                    break;

                  case 't':
                    pw.print(record.getThreadID());
                    break;

                  case 'x':
                    if (record.getThrown()!=null) {
                        if ("notrace".equals(postmod)) {
                            pw.print(record.getThrown());
                        } else {
                            record.getThrown().printStackTrace(pw);
                        }
                    }
                    break;

                  case '%':
                    pw.print("%");
                    break;

                  case 'i':
                    pw.print(record.getSequenceNumber());
                    break;

                  case 'P':
                    Object[] params = record.getParameters();
                    try {
                        if (postmod!=null) {
                            int mod = Integer.parseInt(postmod);
                            if (params!=null && params.length > mod) {
                                pw.print(params[mod]);
                            }
                        } else if (params==null) {
                            pw.print(params);
                        } else {
                            pw.print(Arrays.asList(params));
                        }
                    } catch (Exception e) { }
                    break;
                }
                premod.setLength(0);
            } else {
                pw.write(c);
            }
        }
        return sw.toString().trim() + "\n";
    }
}