123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /* XMLFormatter.java --
- A class for formatting log messages into a standard XML format
- Copyright (C) 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 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 gnu.java.lang.CPStringBuilder;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.ResourceBundle;
- /**
- * 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 CPStringBuilder, provided that the element content is
- * actually existing.
- *
- * @param buf the CPStringBuilder 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 void appendTag(CPStringBuilder 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 CPStringBuilder.
- *
- * @param buf the CPStringBuilder 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 void appendTag(CPStringBuilder buf, int indent,
- String tag, long content)
- {
- appendTag(buf, indent, tag, Long.toString(content));
- }
- public String format(LogRecord record)
- {
- CPStringBuilder buf = new CPStringBuilder(400);
- Level level = record.getLevel();
- long millis = record.getMillis();
- Object[] params = record.getParameters();
- ResourceBundle bundle = record.getResourceBundle();
- String message;
- buf.append("<record>");
- buf.append(lineSep);
- appendTag(buf, 1, "date", iso8601.format(new Date(millis)));
- appendTag(buf, 1, "millis", millis);
- 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 h 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)
- {
- CPStringBuilder buf;
- String encoding;
- buf = new CPStringBuilder(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;
- }
- }
|