001 /* XMLFormatter.java -- 002 A class for formatting log messages into a standard XML format 003 Copyright (C) 2002, 2004 Free Software Foundation, Inc. 004 005 This file is part of GNU Classpath. 006 007 GNU Classpath is free software; you can redistribute it and/or modify 008 it under the terms of the GNU General Public License as published by 009 the Free Software Foundation; either version 2, or (at your option) 010 any later version. 011 012 GNU Classpath is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of 014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 General Public License for more details. 016 017 You should have received a copy of the GNU General Public License 018 along with GNU Classpath; see the file COPYING. If not, write to the 019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 020 02110-1301 USA. 021 022 Linking this library statically or dynamically with other modules is 023 making a combined work based on this library. Thus, the terms and 024 conditions of the GNU General Public License cover the whole 025 combination. 026 027 As a special exception, the copyright holders of this library give you 028 permission to link this library with independent modules to produce an 029 executable, regardless of the license terms of these independent 030 modules, and to copy and distribute the resulting executable under 031 terms of your choice, provided that you also meet, for each linked 032 independent module, the terms and conditions of the license of that 033 module. An independent module is a module which is not derived from 034 or based on this library. If you modify this library, you may extend 035 this exception to your version of the library, but you are not 036 obligated to do so. If you do not wish to do so, delete this 037 exception statement from your version. */ 038 039 040 package java.util.logging; 041 042 import java.text.SimpleDateFormat; 043 import java.util.Date; 044 import java.util.ResourceBundle; 045 046 /** 047 * An <code>XMLFormatter</code> formats LogRecords into 048 * a standard XML format. 049 * 050 * @author Sascha Brawer (brawer@acm.org) 051 */ 052 public class XMLFormatter 053 extends Formatter 054 { 055 /** 056 * Constructs a new XMLFormatter. 057 */ 058 public XMLFormatter() 059 { 060 } 061 062 063 /** 064 * The character sequence that is used to separate lines in the 065 * generated XML stream. Somewhat surprisingly, the Sun J2SE 1.4 066 * reference implementation always uses UNIX line endings, even on 067 * platforms that have different line ending conventions (i.e., 068 * DOS). The GNU Classpath implementation does not replicates this 069 * bug. 070 * 071 * See also the Sun bug parade, bug #4462871, 072 * "java.util.logging.SimpleFormatter uses hard-coded line separator". 073 */ 074 private static final String lineSep = SimpleFormatter.lineSep; 075 076 077 /** 078 * A DateFormat for emitting time in the ISO 8601 format. 079 * Since the API specification of SimpleDateFormat does not talk 080 * about its thread-safety, we cannot share a singleton instance. 081 */ 082 private final SimpleDateFormat iso8601 083 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 084 085 086 /** 087 * Appends a line consisting of indentation, opening element tag, 088 * element content, closing element tag and line separator to 089 * a StringBuffer, provided that the element content is 090 * actually existing. 091 * 092 * @param buf the StringBuffer to which the line will be appended. 093 * 094 * @param indent the indentation level. 095 * 096 * @param tag the element tag name, for instance <code>method</code>. 097 * 098 * @param content the element content, or <code>null</code> to 099 * 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 }