GNU Classpath (0.95) | |
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 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("&"); 131: break; 132: 133: case '<': 134: buf.append("<"); 135: break; 136: 137: case '>': 138: buf.append(">"); 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 Ӓ 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: }
GNU Classpath (0.95) |