001 /*
002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.util.logging;
027
028 import java.io.*;
029 import java.nio.charset.Charset;
030 import java.util.*;
031
032 /**
033 * Format a LogRecord into a standard XML format.
034 * <p>
035 * The DTD specification is provided as Appendix A to the
036 * Java Logging APIs specification.
037 * <p>
038 * The XMLFormatter can be used with arbitrary character encodings,
039 * but it is recommended that it normally be used with UTF-8. The
040 * character encoding can be set on the output Handler.
041 *
042 * @version 1.33, 05/05/07
043 * @since 1.4
044 */
045
046 public class XMLFormatter extends Formatter {
047 private LogManager manager = LogManager.getLogManager();
048
049 // Append a two digit number.
050 private void a2(StringBuffer sb, int x) {
051 if (x < 10) {
052 sb.append('0');
053 }
054 sb.append(x);
055 }
056
057 // Append the time and date in ISO 8601 format
058 private void appendISO8601(StringBuffer sb, long millis) {
059 Date date = new Date(millis);
060 sb.append(date.getYear() + 1900);
061 sb.append('-');
062 a2(sb, date.getMonth() + 1);
063 sb.append('-');
064 a2(sb, date.getDate());
065 sb.append('T');
066 a2(sb, date.getHours());
067 sb.append(':');
068 a2(sb, date.getMinutes());
069 sb.append(':');
070 a2(sb, date.getSeconds());
071 }
072
073 // Append to the given StringBuffer an escaped version of the
074 // given text string where XML special characters have been escaped.
075 // For a null string we append "<null>"
076 private void escape(StringBuffer sb, String text) {
077 if (text == null) {
078 text = "<null>";
079 }
080 for (int i = 0; i < text.length(); i++) {
081 char ch = text.charAt(i);
082 if (ch == '<') {
083 sb.append("<");
084 } else if (ch == '>') {
085 sb.append(">");
086 } else if (ch == '&') {
087 sb.append("&");
088 } else {
089 sb.append(ch);
090 }
091 }
092 }
093
094 /**
095 * Format the given message to XML.
096 * <p>
097 * This method can be overridden in a subclass.
098 * It is recommended to use the {@link Formatter#formatMessage}
099 * convenience method to localize and format the message field.
100 *
101 * @param record the log record to be formatted.
102 * @return a formatted log record
103 */
104 public String format(LogRecord record) {
105 StringBuffer sb = new StringBuffer(500);
106 sb.append("<record>\n");
107
108 sb.append(" <date>");
109 appendISO8601(sb, record.getMillis());
110 sb.append("</date>\n");
111
112 sb.append(" <millis>");
113 sb.append(record.getMillis());
114 sb.append("</millis>\n");
115
116 sb.append(" <sequence>");
117 sb.append(record.getSequenceNumber());
118 sb.append("</sequence>\n");
119
120 String name = record.getLoggerName();
121 if (name != null) {
122 sb.append(" <logger>");
123 escape(sb, name);
124 sb.append("</logger>\n");
125 }
126
127 sb.append(" <level>");
128 escape(sb, record.getLevel().toString());
129 sb.append("</level>\n");
130
131 if (record.getSourceClassName() != null) {
132 sb.append(" <class>");
133 escape(sb, record.getSourceClassName());
134 sb.append("</class>\n");
135 }
136
137 if (record.getSourceMethodName() != null) {
138 sb.append(" <method>");
139 escape(sb, record.getSourceMethodName());
140 sb.append("</method>\n");
141 }
142
143 sb.append(" <thread>");
144 sb.append(record.getThreadID());
145 sb.append("</thread>\n");
146
147 if (record.getMessage() != null) {
148 // Format the message string and its accompanying parameters.
149 String message = formatMessage(record);
150 sb.append(" <message>");
151 escape(sb, message);
152 sb.append("</message>");
153 sb.append("\n");
154 }
155
156 // If the message is being localized, output the key, resource
157 // bundle name, and params.
158 ResourceBundle bundle = record.getResourceBundle();
159 try {
160 if (bundle != null
161 && bundle.getString(record.getMessage()) != null) {
162 sb.append(" <key>");
163 escape(sb, record.getMessage());
164 sb.append("</key>\n");
165 sb.append(" <catalog>");
166 escape(sb, record.getResourceBundleName());
167 sb.append("</catalog>\n");
168 }
169 } catch (Exception ex) {
170 // The message is not in the catalog. Drop through.
171 }
172
173 Object parameters[] = record.getParameters();
174 // Check to see if the parameter was not a messagetext format
175 // or was not null or empty
176 if (parameters != null && parameters.length != 0
177 && record.getMessage().indexOf("{") == -1) {
178 for (int i = 0; i < parameters.length; i++) {
179 sb.append(" <param>");
180 try {
181 escape(sb, parameters[i].toString());
182 } catch (Exception ex) {
183 sb.append("???");
184 }
185 sb.append("</param>\n");
186 }
187 }
188
189 if (record.getThrown() != null) {
190 // Report on the state of the throwable.
191 Throwable th = record.getThrown();
192 sb.append(" <exception>\n");
193 sb.append(" <message>");
194 escape(sb, th.toString());
195 sb.append("</message>\n");
196 StackTraceElement trace[] = th.getStackTrace();
197 for (int i = 0; i < trace.length; i++) {
198 StackTraceElement frame = trace[i];
199 sb.append(" <frame>\n");
200 sb.append(" <class>");
201 escape(sb, frame.getClassName());
202 sb.append("</class>\n");
203 sb.append(" <method>");
204 escape(sb, frame.getMethodName());
205 sb.append("</method>\n");
206 // Check for a line number.
207 if (frame.getLineNumber() >= 0) {
208 sb.append(" <line>");
209 sb.append(frame.getLineNumber());
210 sb.append("</line>\n");
211 }
212 sb.append(" </frame>\n");
213 }
214 sb.append(" </exception>\n");
215 }
216
217 sb.append("</record>\n");
218 return sb.toString();
219 }
220
221 /**
222 * Return the header string for a set of XML formatted records.
223 *
224 * @param h The target handler (can be null)
225 * @return a valid XML string
226 */
227 public String getHead(Handler h) {
228 StringBuffer sb = new StringBuffer();
229 String encoding;
230 sb.append("<?xml version=\"1.0\"");
231
232 if (h != null) {
233 encoding = h.getEncoding();
234 } else {
235 encoding = null;
236 }
237
238 if (encoding == null) {
239 // Figure out the default encoding.
240 encoding = java.nio.charset.Charset.defaultCharset().name();
241 }
242 // Try to map the encoding name to a canonical name.
243 try {
244 Charset cs = Charset.forName(encoding);
245 encoding = cs.name();
246 } catch (Exception ex) {
247 // We hit problems finding a canonical name.
248 // Just use the raw encoding name.
249 }
250
251 sb.append(" encoding=\"");
252 sb.append(encoding);
253 sb.append("\"");
254 sb.append(" standalone=\"no\"?>\n");
255 sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
256 sb.append("<log>\n");
257 return sb.toString();
258 }
259
260 /**
261 * Return the tail string for a set of XML formatted records.
262 *
263 * @param h The target handler (can be null)
264 * @return a valid XML string
265 */
266 public String getTail(Handler h) {
267 return "</log>\n";
268 }
269 }
|