Source for java.util.logging.XMLFormatter

   1: /* XMLFormatter.java --
   2:    A class for formatting log messages into a standard XML format
   3:    Copyright (C) 2002, 2004 Free Software Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: 
  40: package java.util.logging;
  41: 
  42: import java.text.SimpleDateFormat;
  43: import java.util.Date;
  44: import java.util.ResourceBundle;
  45: 
  46: /**
  47:  * An <code>XMLFormatter</code> formats LogRecords into
  48:  * a standard XML format.
  49:  *
  50:  * @author Sascha Brawer (brawer@acm.org)
  51:  */
  52: public class XMLFormatter
  53:   extends Formatter
  54: {
  55:   /**
  56:    * Constructs a new XMLFormatter.
  57:    */
  58:   public XMLFormatter()
  59:   {
  60:   }
  61: 
  62: 
  63:   /**
  64:    * The character sequence that is used to separate lines in the
  65:    * generated XML stream. Somewhat surprisingly, the Sun J2SE 1.4
  66:    * reference implementation always uses UNIX line endings, even on
  67:    * platforms that have different line ending conventions (i.e.,
  68:    * DOS). The GNU Classpath implementation does not replicates this
  69:    * bug.
  70:    *
  71:    * See also the Sun bug parade, bug #4462871,
  72:    * "java.util.logging.SimpleFormatter uses hard-coded line separator".
  73:    */
  74:   private static final String lineSep = SimpleFormatter.lineSep;
  75: 
  76:     
  77:   /**
  78:    * A DateFormat for emitting time in the ISO 8601 format.
  79:    * Since the API specification of SimpleDateFormat does not talk
  80:    * about its thread-safety, we cannot share a singleton instance.
  81:    */
  82:   private final SimpleDateFormat iso8601
  83:     = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  84: 
  85: 
  86:   /**
  87:    * Appends a line consisting of indentation, opening element tag,
  88:    * element content, closing element tag and line separator to
  89:    * a StringBuffer, provided that the element content is
  90:    * actually existing.
  91:    *
  92:    * @param buf the StringBuffer to which the line will be appended.
  93:    *
  94:    * @param indent the indentation level.
  95:    *
  96:    * @param tag the element tag name, for instance <code>method</code>.
  97:    *
  98:    * @param content the element content, or <code>null</code> to
  99:    *        have no output whatsoever appended to <code>buf</code>.
 100:    */
 101:   private static void appendTag(StringBuffer buf, int indent,
 102:                                 String tag, String content)
 103:   {
 104:     int i;
 105: 
 106:     if (content == null)
 107:       return;
 108: 
 109:     for (i = 0; i < indent * 2; i++)
 110:       buf.append(' ');
 111: 
 112:     buf.append("<");
 113:     buf.append(tag);
 114:     buf.append('>');
 115: 
 116:     /* Append the content, but escape for XML by replacing
 117:      * '&', '<', '>' and all non-ASCII characters with
 118:      * appropriate escape sequences.
 119:      * The Sun J2SE 1.4 reference implementation does not
 120:      * escape non-ASCII characters. This is a bug in their
 121:      * implementation which has been reported in the Java
 122:      * bug parade as bug number (FIXME: Insert number here).
 123:      */
 124:     for (i = 0; i < content.length(); i++)
 125:     {
 126:       char c = content.charAt(i);
 127:       switch (c)
 128:       {
 129:       case '&':
 130:     buf.append("&amp;");
 131:     break;
 132: 
 133:       case '<':
 134:     buf.append("&lt;");
 135:     break;
 136: 
 137:       case '>':
 138:     buf.append("&gt;");
 139:     break;
 140: 
 141:       default:
 142:     if (((c >= 0x20) && (c <= 0x7e))
 143:         || (c == /* line feed */ 10)
 144:         || (c == /* carriage return */ 13))
 145:       buf.append(c);
 146:     else
 147:     {
 148:       buf.append("&#");
 149:       buf.append((int) c);
 150:       buf.append(';');
 151:     }
 152:     break;
 153:       } /* switch (c) */
 154:     } /* for i */
 155: 
 156:     buf.append("</");
 157:     buf.append(tag);
 158:     buf.append(">");
 159:     buf.append(lineSep);
 160:   }
 161: 
 162: 
 163:   /**
 164:    * Appends a line consisting of indentation, opening element tag,
 165:    * numeric element content, closing element tag and line separator
 166:    * to a StringBuffer.
 167:    *
 168:    * @param buf the StringBuffer to which the line will be appended.
 169:    *
 170:    * @param indent the indentation level.
 171:    *
 172:    * @param tag the element tag name, for instance <code>method</code>.
 173:    *
 174:    * @param content the element content.
 175:    */
 176:   private static void appendTag(StringBuffer buf, int indent,
 177:                                 String tag, long content)
 178:   {
 179:     appendTag(buf, indent, tag, Long.toString(content));
 180:   }
 181: 
 182: 
 183:   public String format(LogRecord record)
 184:   {
 185:     StringBuffer    buf = new StringBuffer(400);
 186:     Level           level = record.getLevel();
 187:     long            millis = record.getMillis();
 188:     Object[]        params = record.getParameters();
 189:     ResourceBundle  bundle = record.getResourceBundle();
 190:     String          message;
 191:     
 192:     buf.append("<record>");
 193:     buf.append(lineSep);
 194:     
 195:     
 196:     appendTag(buf, 1, "date", iso8601.format(new Date(millis)));
 197:     appendTag(buf, 1, "millis", millis);
 198:     appendTag(buf, 1, "sequence", record.getSequenceNumber());
 199:     appendTag(buf, 1, "logger", record.getLoggerName());
 200: 
 201:     if (level.isStandardLevel())
 202:       appendTag(buf, 1, "level", level.toString());
 203:     else
 204:       appendTag(buf, 1, "level", level.intValue());
 205: 
 206:     appendTag(buf, 1, "class", record.getSourceClassName());
 207:     appendTag(buf, 1, "method", record.getSourceMethodName());
 208:     appendTag(buf, 1, "thread", record.getThreadID());
 209: 
 210:     /* The Sun J2SE 1.4 reference implementation does not emit the
 211:      * message in localized form. This is in violation of the API
 212:      * specification. The GNU Classpath implementation intentionally
 213:      * replicates the buggy behavior of the Sun implementation, as
 214:      * different log files might be a big nuisance to users.
 215:      */
 216:     try
 217:     {
 218:       record.setResourceBundle(null);
 219:       message = formatMessage(record);
 220:     }
 221:     finally
 222:     {
 223:       record.setResourceBundle(bundle);
 224:     }
 225:     appendTag(buf, 1, "message", message);
 226: 
 227:     /* The Sun J2SE 1.4 reference implementation does not
 228:      * emit key, catalog and param tags. This is in violation
 229:      * of the API specification.  The Classpath implementation
 230:      * intentionally replicates the buggy behavior of the
 231:      * Sun implementation, as different log files might be
 232:      * a big nuisance to users.
 233:      *
 234:      * FIXME: File a bug report with Sun. Insert bug number here.
 235:      *
 236:      *
 237:      * key = record.getMessage();
 238:      * if (key == null)
 239:      *   key = "";
 240:      *
 241:      * if ((bundle != null) && !key.equals(message))
 242:      * {
 243:      *   appendTag(buf, 1, "key", key);
 244:      *   appendTag(buf, 1, "catalog", record.getResourceBundleName());
 245:      * }
 246:      *
 247:      * if (params != null)
 248:      * {
 249:      *   for (int i = 0; i < params.length; i++)
 250:      *     appendTag(buf, 1, "param", params[i].toString());
 251:      * }
 252:      */
 253: 
 254:     /* FIXME: We have no way to obtain the stacktrace before free JVMs
 255:      * support the corresponding method in java.lang.Throwable.  Well,
 256:      * it would be possible to parse the output of printStackTrace,
 257:      * but this would be pretty kludgy. Instead, we postpose the
 258:      * implementation until Throwable has made progress.
 259:      */
 260:     Throwable thrown = record.getThrown();
 261:     if (thrown != null)
 262:     {
 263:       buf.append("  <exception>");
 264:       buf.append(lineSep);
 265: 
 266:       /* The API specification is not clear about what exactly
 267:        * goes into the XML record for a thrown exception: It
 268:        * could be the result of getMessage(), getLocalizedMessage(),
 269:        * or toString(). Therefore, it was necessary to write a
 270:        * Mauve testlet and run it with the Sun J2SE 1.4 reference
 271:        * implementation. It turned out that the we need to call
 272:        * toString().
 273:        *
 274:        * FIXME: File a bug report with Sun, asking for clearer
 275:        * specs.
 276:        */
 277:       appendTag(buf, 2, "message", thrown.toString());
 278: 
 279:       /* FIXME: The Logging DTD specifies:
 280:        *
 281:        * <!ELEMENT exception (message?, frame+)>
 282:        *
 283:        * However, java.lang.Throwable.getStackTrace() is
 284:        * allowed to return an empty array. So, what frame should
 285:        * be emitted for an empty stack trace? We probably
 286:        * should file a bug report with Sun, asking for the DTD
 287:        * to be changed.
 288:        */
 289: 
 290:       buf.append("  </exception>");
 291:       buf.append(lineSep);
 292:     }
 293: 
 294: 
 295:     buf.append("</record>");
 296:     buf.append(lineSep);
 297: 
 298:     return buf.toString();
 299:   }
 300: 
 301: 
 302:   /**
 303:    * Returns a string that handlers are supposed to emit before
 304:    * the first log record.  The base implementation returns an
 305:    * empty string, but subclasses such as {@link XMLFormatter}
 306:    * override this method in order to provide a suitable header.
 307:    *
 308:    * @return a string for the header.
 309:    *
 310:    * @param h the handler which will prepend the returned
 311:    *     string in front of the first log record.  This method
 312:    *     will inspect certain properties of the handler, for
 313:    *     example its encoding, in order to construct the header.
 314:    */
 315:   public String getHead(Handler h)
 316:   {
 317:     StringBuffer  buf;
 318:     String        encoding;
 319: 
 320:     buf = new StringBuffer(80);
 321:     buf.append("<?xml version=\"1.0\" encoding=\"");
 322: 
 323:     encoding = h.getEncoding();
 324: 
 325:     /* file.encoding is a system property with the Sun JVM, indicating
 326:      * the platform-default file encoding. Unfortunately, the API
 327:      * specification for java.lang.System.getProperties() does not
 328:      * list this property.
 329:      */
 330:     if (encoding == null)
 331:       encoding = System.getProperty("file.encoding");
 332: 
 333:     /* Since file.encoding is not listed with the API specification of
 334:      * java.lang.System.getProperties(), there might be some VMs that
 335:      * do not define this system property.  Therefore, we use UTF-8 as
 336:      * a reasonable default. Please note that if the platform encoding
 337:      * uses the same codepoints as US-ASCII for the US-ASCII character
 338:      * set (e.g, 65 for A), it does not matter whether we emit the
 339:      * wrong encoding into the XML header -- the GNU Classpath will
 340:      * emit XML escape sequences like &#1234; for any non-ASCII
 341:      * character.  Virtually all character encodings use the same code
 342:      * points as US-ASCII for ASCII characters.  Probably, EBCDIC is
 343:      * the only exception.
 344:      */
 345:     if (encoding == null)
 346:       encoding = "UTF-8";
 347:     
 348:     /* On Windows XP localized for Swiss German (this is one of
 349:      * my [Sascha Brawer's] test machines), the default encoding
 350:      * has the canonical name "windows-1252". The "historical" name
 351:      * of this encoding is "Cp1252" (see the Javadoc for the class
 352:      * java.nio.charset.Charset for the distinction). Now, that class
 353:      * does have a method for mapping historical to canonical encoding
 354:      * names. However, if we used it here, we would be come dependent
 355:      * on java.nio.*, which was only introduced with J2SE 1.4.
 356:      * Thus, we do this little hack here. As soon as Classpath supports
 357:      * java.nio.charset.CharSet, this hack should be replaced by
 358:      * code that correctly canonicalizes the encoding name.
 359:      */
 360:     if ((encoding.length() > 2) && encoding.startsWith("Cp"))
 361:       encoding = "windows-" + encoding.substring(2);
 362: 
 363:     buf.append(encoding);
 364: 
 365:     buf.append("\" standalone=\"no\"?>");
 366:     buf.append(lineSep);
 367: 
 368:     /* SYSTEM is not a fully qualified URL so that validating
 369:      * XML parsers do not need to connect to the Internet in
 370:      * order to read in a log file.  See also the Sun Bug Parade,
 371:      * bug #4372790, "Logging APIs: need to use relative URL for XML
 372:      * doctype".
 373:      */
 374:     buf.append("<!DOCTYPE log SYSTEM \"logger.dtd\">");
 375:     buf.append(lineSep);
 376:     buf.append("<log>");
 377:     buf.append(lineSep);
 378: 
 379:     return buf.toString();
 380:   }
 381: 
 382: 
 383:   public String getTail(Handler h)
 384:   {
 385:     return "</log>" + lineSep;
 386:   }
 387: }