Merge from mainline
[official-gcc.git] / libjava / classpath / java / util / logging / XMLFormatter.java
blob8f5769be1b9483a014873d810507b4f3bf855e4b
1 /* XMLFormatter.java --
2 A class for formatting log messages into a standard XML format
3 Copyright (C) 2002, 2004 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
40 package java.util.logging;
42 import java.text.SimpleDateFormat;
43 import java.util.Date;
44 import java.util.ResourceBundle;
46 /**
47 * An <code>XMLFormatter</code> formats LogRecords into
48 * a standard XML format.
50 * @author Sascha Brawer (brawer@acm.org)
52 public class XMLFormatter
53 extends Formatter
55 /**
56 * Constructs a new XMLFormatter.
58 public XMLFormatter()
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.
71 * See also the Sun bug parade, bug #4462871,
72 * "java.util.logging.SimpleFormatter uses hard-coded line separator".
74 private static final String lineSep = SimpleFormatter.lineSep;
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.
82 private final SimpleDateFormat iso8601
83 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
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.
92 * @param buf the StringBuffer to which the line will be appended.
94 * @param indent the indentation level.
96 * @param tag the element tag name, for instance <code>method</code>.
98 * @param content the element content, or <code>null</code> to
99 * have no output whatsoever appended to <code>buf</code>.
101 private static void appendTag(StringBuffer buf, int indent,
102 String tag, String content)
104 int i;
106 if (content == null)
107 return;
109 for (i = 0; i < indent * 2; i++)
110 buf.append(' ');
112 buf.append("<");
113 buf.append(tag);
114 buf.append('>');
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).
124 for (i = 0; i < content.length(); i++)
126 char c = content.charAt(i);
127 switch (c)
129 case '&':
130 buf.append("&amp;");
131 break;
133 case '<':
134 buf.append("&lt;");
135 break;
137 case '>':
138 buf.append("&gt;");
139 break;
141 default:
142 if (((c >= 0x20) && (c <= 0x7e))
143 || (c == /* line feed */ 10)
144 || (c == /* carriage return */ 13))
145 buf.append(c);
146 else
148 buf.append("&#");
149 buf.append((int) c);
150 buf.append(';');
152 break;
153 } /* switch (c) */
154 } /* for i */
156 buf.append("</");
157 buf.append(tag);
158 buf.append(">");
159 buf.append(lineSep);
164 * Appends a line consisting of indentation, opening element tag,
165 * numeric element content, closing element tag and line separator
166 * to a StringBuffer.
168 * @param buf the StringBuffer to which the line will be appended.
170 * @param indent the indentation level.
172 * @param tag the element tag name, for instance <code>method</code>.
174 * @param content the element content.
176 private static void appendTag(StringBuffer buf, int indent,
177 String tag, long content)
179 appendTag(buf, indent, tag, Long.toString(content));
183 public String format(LogRecord record)
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;
192 buf.append("<record>");
193 buf.append(lineSep);
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());
201 if (level.isStandardLevel())
202 appendTag(buf, 1, "level", level.toString());
203 else
204 appendTag(buf, 1, "level", level.intValue());
206 appendTag(buf, 1, "class", record.getSourceClassName());
207 appendTag(buf, 1, "method", record.getSourceMethodName());
208 appendTag(buf, 1, "thread", record.getThreadID());
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.
218 record.setResourceBundle(null);
219 message = formatMessage(record);
221 finally
223 record.setResourceBundle(bundle);
225 appendTag(buf, 1, "message", message);
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.
234 * FIXME: File a bug report with Sun. Insert bug number here.
237 * key = record.getMessage();
238 * if (key == null)
239 * key = "";
241 * if ((bundle != null) && !key.equals(message))
243 * appendTag(buf, 1, "key", key);
244 * appendTag(buf, 1, "catalog", record.getResourceBundleName());
247 * if (params != null)
249 * for (int i = 0; i < params.length; i++)
250 * appendTag(buf, 1, "param", params[i].toString());
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.
260 Throwable thrown = record.getThrown();
261 if (thrown != null)
263 buf.append(" <exception>");
264 buf.append(lineSep);
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().
274 * FIXME: File a bug report with Sun, asking for clearer
275 * specs.
277 appendTag(buf, 2, "message", thrown.toString());
279 /* FIXME: The Logging DTD specifies:
281 * <!ELEMENT exception (message?, frame+)>
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.
290 buf.append(" </exception>");
291 buf.append(lineSep);
295 buf.append("</record>");
296 buf.append(lineSep);
298 return buf.toString();
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.
308 * @return a string for the header.
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.
315 public String getHead(Handler h)
317 StringBuffer buf;
318 String encoding;
320 buf = new StringBuffer(80);
321 buf.append("<?xml version=\"1.0\" encoding=\"");
323 encoding = h.getEncoding();
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.
330 if (encoding == null)
331 encoding = System.getProperty("file.encoding");
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.
345 if (encoding == null)
346 encoding = "UTF-8";
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.
360 if ((encoding.length() > 2) && encoding.startsWith("Cp"))
361 encoding = "windows-" + encoding.substring(2);
363 buf.append(encoding);
365 buf.append("\" standalone=\"no\"?>");
366 buf.append(lineSep);
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".
374 buf.append("<!DOCTYPE log SYSTEM \"logger.dtd\">");
375 buf.append(lineSep);
376 buf.append("<log>");
377 buf.append(lineSep);
379 return buf.toString();
383 public String getTail(Handler h)
385 return "</log>" + lineSep;