Frames | No Frames |
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("&"); 133: break; 134: 135: case '<': 136: buf.append("<"); 137: break; 138: 139: case '>': 140: buf.append(">"); 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 Ӓ 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: }