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