diff options
Diffstat (limited to 'libjava/java/util/logging/XMLFormatter.java')
-rw-r--r-- | libjava/java/util/logging/XMLFormatter.java | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/libjava/java/util/logging/XMLFormatter.java b/libjava/java/util/logging/XMLFormatter.java new file mode 100644 index 0000000..fbaab1c --- /dev/null +++ b/libjava/java/util/logging/XMLFormatter.java @@ -0,0 +1,395 @@ +/* XMLFormatter.java + -- a class for formatting log messages into a standard XML format + +Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + +*/ + + +package java.util.logging; + +import java.util.Date; +import java.util.ResourceBundle; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; + +/** + * An <code>XMLFormatter</code> formats LogRecords into + * a standard XML format. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class XMLFormatter + extends Formatter +{ + /** + * Constructs a new XMLFormatter. + */ + public XMLFormatter() + { + } + + + /** + * The character sequence that is used to separate lines in the + * generated XML stream. Somewhat surprisingly, the Sun J2SE 1.4 + * reference implementation always uses UNIX line endings, even on + * platforms that have different line ending conventions (i.e., + * DOS). The GNU Classpath implementation does not replicates this + * bug. + * + * See also the Sun bug parade, bug #4462871, + * "java.util.logging.SimpleFormatter uses hard-coded line separator". + */ + private static final String lineSep = SimpleFormatter.lineSep; + + + /** + * A DateFormat for emitting time in the ISO 8601 format. + * Since the API specification of SimpleDateFormat does not talk + * about its thread-safety, we cannot share a singleton instance. + */ + private final SimpleDateFormat iso8601 + = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + + /** + * Appends a line consisting of indentation, opening element tag, + * element content, closing element tag and line separator to + * a StringBuffer, provided that the element content is + * actually existing. + * + * @param buf the StringBuffer to which the line will be appended. + * + * @param indent the indentation level. + * + * @param tag the element tag name, for instance <code>method</code>. + * + * @param content the element content, or <code>null</code> to + * have no output whatsoever appended to <code>buf</code>. + */ + private static final void appendTag(StringBuffer buf, + int indent, + String tag, + String content) + { + int i; + + if (content == null) + return; + + for (i = 0; i < indent * 2; i++) + buf.append(' '); + + buf.append("<"); + buf.append(tag); + buf.append('>'); + + /* Append the content, but escape for XML by replacing + * '&', '<', '>' and all non-ASCII characters with + * appropriate escape sequences. + * The Sun J2SE 1.4 reference implementation does not + * escape non-ASCII characters. This is a bug in their + * implementation which has been reported in the Java + * bug parade as bug number (FIXME: Insert number here). + */ + for (i = 0; i < content.length(); i++) + { + char c = content.charAt(i); + switch (c) + { + case '&': + buf.append("&"); + break; + + case '<': + buf.append("<"); + break; + + case '>': + buf.append(">"); + break; + + default: + if (((c >= 0x20) && (c <= 0x7e)) + || (c == /* line feed */ 10) + || (c == /* carriage return */ 13)) + buf.append(c); + else + { + buf.append("&#"); + buf.append((int) c); + buf.append(';'); + } + break; + } /* switch (c) */ + } /* for i */ + + buf.append("</"); + buf.append(tag); + buf.append(">"); + buf.append(lineSep); + } + + + /** + * Appends a line consisting of indentation, opening element tag, + * numeric element content, closing element tag and line separator + * to a StringBuffer. + * + * @param buf the StringBuffer to which the line will be appended. + * + * @param indent the indentation level. + * + * @param tag the element tag name, for instance <code>method</code>. + * + * @param content the element content. + */ + private static final void appendTag(StringBuffer buf, + int indent, + String tag, + long content) + { + appendTag(buf, indent, tag, Long.toString(content)); + } + + + public String format(LogRecord record) + { + StringBuffer buf = new StringBuffer(400); + Level level = record.getLevel(); + long millis = record.getMillis(); + Object[] params = record.getParameters(); + ResourceBundle bundle = record.getResourceBundle(); + String key, message; + + buf.append("<record>"); + buf.append(lineSep); + + + appendTag(buf, 1, "date", iso8601.format(new Date(millis))); + appendTag(buf, 1, "millis", record.getMillis()); + appendTag(buf, 1, "sequence", record.getSequenceNumber()); + appendTag(buf, 1, "logger", record.getLoggerName()); + + if (level.isStandardLevel()) + appendTag(buf, 1, "level", level.toString()); + else + appendTag(buf, 1, "level", level.intValue()); + + appendTag(buf, 1, "class", record.getSourceClassName()); + appendTag(buf, 1, "method", record.getSourceMethodName()); + appendTag(buf, 1, "thread", record.getThreadID()); + + /* The Sun J2SE 1.4 reference implementation does not emit the + * message in localized form. This is in violation of the API + * specification. The GNU Classpath implementation intentionally + * replicates the buggy behavior of the Sun implementation, as + * different log files might be a big nuisance to users. + */ + try + { + record.setResourceBundle(null); + message = formatMessage(record); + } + finally + { + record.setResourceBundle(bundle); + } + appendTag(buf, 1, "message", message); + + /* The Sun J2SE 1.4 reference implementation does not + * emit key, catalog and param tags. This is in violation + * of the API specification. The Classpath implementation + * intentionally replicates the buggy behavior of the + * Sun implementation, as different log files might be + * a big nuisance to users. + * + * FIXME: File a bug report with Sun. Insert bug number here. + * + * + * key = record.getMessage(); + * if (key == null) + * key = ""; + * + * if ((bundle != null) && !key.equals(message)) + * { + * appendTag(buf, 1, "key", key); + * appendTag(buf, 1, "catalog", record.getResourceBundleName()); + * } + * + * if (params != null) + * { + * for (int i = 0; i < params.length; i++) + * appendTag(buf, 1, "param", params[i].toString()); + * } + */ + + /* FIXME: We have no way to obtain the stacktrace before free JVMs + * support the corresponding method in java.lang.Throwable. Well, + * it would be possible to parse the output of printStackTrace, + * but this would be pretty kludgy. Instead, we postpose the + * implementation until Throwable has made progress. + */ + Throwable thrown = record.getThrown(); + if (thrown != null) + { + buf.append(" <exception>"); + buf.append(lineSep); + + /* The API specification is not clear about what exactly + * goes into the XML record for a thrown exception: It + * could be the result of getMessage(), getLocalizedMessage(), + * or toString(). Therefore, it was necessary to write a + * Mauve testlet and run it with the Sun J2SE 1.4 reference + * implementation. It turned out that the we need to call + * toString(). + * + * FIXME: File a bug report with Sun, asking for clearer + * specs. + */ + appendTag(buf, 2, "message", thrown.toString()); + + /* FIXME: The Logging DTD specifies: + * + * <!ELEMENT exception (message?, frame+)> + * + * However, java.lang.Throwable.getStackTrace() is + * allowed to return an empty array. So, what frame should + * be emitted for an empty stack trace? We probably + * should file a bug report with Sun, asking for the DTD + * to be changed. + */ + + buf.append(" </exception>"); + buf.append(lineSep); + } + + + buf.append("</record>"); + buf.append(lineSep); + + return buf.toString(); + } + + + /** + * Returns a string that handlers are supposed to emit before + * the first log record. The base implementation returns an + * empty string, but subclasses such as {@link XMLFormatter} + * override this method in order to provide a suitable header. + * + * @return a string for the header. + * + * @param handler the handler which will prepend the returned + * string in front of the first log record. This method + * will inspect certain properties of the handler, for + * example its encoding, in order to construct the header. + */ + public String getHead(Handler h) + { + StringBuffer buf; + String encoding; + + buf = new StringBuffer(80); + buf.append("<?xml version=\"1.0\" encoding=\""); + + encoding = h.getEncoding(); + + /* file.encoding is a system property with the Sun JVM, indicating + * the platform-default file encoding. Unfortunately, the API + * specification for java.lang.System.getProperties() does not + * list this property. + */ + if (encoding == null) + encoding = System.getProperty("file.encoding"); + + /* Since file.encoding is not listed with the API specification of + * java.lang.System.getProperties(), there might be some VMs that + * do not define this system property. Therefore, we use UTF-8 as + * a reasonable default. Please note that if the platform encoding + * uses the same codepoints as US-ASCII for the US-ASCII character + * set (e.g, 65 for A), it does not matter whether we emit the + * wrong encoding into the XML header -- the GNU Classpath will + * emit XML escape sequences like Ӓ for any non-ASCII + * character. Virtually all character encodings use the same code + * points as US-ASCII for ASCII characters. Probably, EBCDIC is + * the only exception. + */ + if (encoding == null) + encoding = "UTF-8"; + + /* On Windows XP localized for Swiss German (this is one of + * my [Sascha Brawer's] test machines), the default encoding + * has the canonical name "windows-1252". The "historical" name + * of this encoding is "Cp1252" (see the Javadoc for the class + * java.nio.charset.Charset for the distinction). Now, that class + * does have a method for mapping historical to canonical encoding + * names. However, if we used it here, we would be come dependent + * on java.nio.*, which was only introduced with J2SE 1.4. + * Thus, we do this little hack here. As soon as Classpath supports + * java.nio.charset.CharSet, this hack should be replaced by + * code that correctly canonicalizes the encoding name. + */ + if ((encoding.length() > 2) && encoding.startsWith("Cp")) + encoding = "windows-" + encoding.substring(2); + + buf.append(encoding); + + buf.append("\" standalone=\"no\"?>"); + buf.append(lineSep); + + /* SYSTEM is not a fully qualified URL so that validating + * XML parsers do not need to connect to the Internet in + * order to read in a log file. See also the Sun Bug Parade, + * bug #4372790, "Logging APIs: need to use relative URL for XML + * doctype". + */ + buf.append("<!DOCTYPE log SYSTEM \"logger.dtd\">"); + buf.append(lineSep); + buf.append("<log>"); + buf.append(lineSep); + + return buf.toString(); + } + + + public String getTail(Handler h) + { + return "</log>" + lineSep; + } +} |