XMLFormatter.java revision 331f0f0d4a4148fc934d44f16829b14def6c10f6
1/*
2 * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26
27package java.util.logging;
28
29import java.io.*;
30import java.nio.charset.Charset;
31import java.util.*;
32
33/**
34 * Format a LogRecord into a standard XML format.
35 * <p>
36 * The DTD specification is provided as Appendix A to the
37 * Java Logging APIs specification.
38 * <p>
39 * The XMLFormatter can be used with arbitrary character encodings,
40 * but it is recommended that it normally be used with UTF-8.  The
41 * character encoding can be set on the output Handler.
42 *
43 * @since 1.4
44 */
45
46public class XMLFormatter extends Formatter {
47    private LogManager manager = LogManager.getLogManager();
48
49    // Append a two digit number.
50    private void a2(StringBuffer sb, int x) {
51        if (x < 10) {
52            sb.append('0');
53        }
54        sb.append(x);
55    }
56
57    // Append the time and date in ISO 8601 format
58    private void appendISO8601(StringBuffer sb, long millis) {
59        Date date = new Date(millis);
60        sb.append(date.getYear() + 1900);
61        sb.append('-');
62        a2(sb, date.getMonth() + 1);
63        sb.append('-');
64        a2(sb, date.getDate());
65        sb.append('T');
66        a2(sb, date.getHours());
67        sb.append(':');
68        a2(sb, date.getMinutes());
69        sb.append(':');
70        a2(sb, date.getSeconds());
71    }
72
73    // Append to the given StringBuffer an escaped version of the
74    // given text string where XML special characters have been escaped.
75    // For a null string we append "<null>"
76    private void escape(StringBuffer sb, String text) {
77        if (text == null) {
78            text = "<null>";
79        }
80        for (int i = 0; i < text.length(); i++) {
81            char ch = text.charAt(i);
82            if (ch == '<') {
83                sb.append("&lt;");
84            } else if (ch == '>') {
85                sb.append("&gt;");
86            } else if (ch == '&') {
87                sb.append("&amp;");
88            } else {
89                sb.append(ch);
90            }
91        }
92    }
93
94    /**
95     * Format the given message to XML.
96     * <p>
97     * This method can be overridden in a subclass.
98     * It is recommended to use the {@link Formatter#formatMessage}
99     * 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        } else {
155            sb.append("<message/>");
156            sb.append("\n");
157        }
158
159        // If the message is being localized, output the key, resource
160        // bundle name, and params.
161        ResourceBundle bundle = record.getResourceBundle();
162        try {
163            if (bundle != null && bundle.getString(record.getMessage()) != null) {
164                sb.append("  <key>");
165                escape(sb, record.getMessage());
166                sb.append("</key>\n");
167                sb.append("  <catalog>");
168                escape(sb, record.getResourceBundleName());
169                sb.append("</catalog>\n");
170            }
171        } catch (Exception ex) {
172            // The message is not in the catalog.  Drop through.
173        }
174
175        Object parameters[] = record.getParameters();
176        //  Check to see if the parameter was not a messagetext format
177        //  or was not null or empty
178        if ( parameters != null && parameters.length != 0
179                && record.getMessage().indexOf("{") == -1 ) {
180            for (int i = 0; i < parameters.length; i++) {
181                sb.append("  <param>");
182                try {
183                    escape(sb, parameters[i].toString());
184                } catch (Exception ex) {
185                    sb.append("???");
186                }
187                sb.append("</param>\n");
188            }
189        }
190
191        if (record.getThrown() != null) {
192            // Report on the state of the throwable.
193            Throwable th = record.getThrown();
194            sb.append("  <exception>\n");
195            sb.append("    <message>");
196            escape(sb, th.toString());
197            sb.append("</message>\n");
198            StackTraceElement trace[] = th.getStackTrace();
199            for (int i = 0; i < trace.length; i++) {
200                StackTraceElement frame = trace[i];
201                sb.append("    <frame>\n");
202                sb.append("      <class>");
203                escape(sb, frame.getClassName());
204                sb.append("</class>\n");
205                sb.append("      <method>");
206                escape(sb, frame.getMethodName());
207                sb.append("</method>\n");
208                // Check for a line number.
209                if (frame.getLineNumber() >= 0) {
210                    sb.append("      <line>");
211                    sb.append(frame.getLineNumber());
212                    sb.append("</line>\n");
213                }
214                sb.append("    </frame>\n");
215            }
216            sb.append("  </exception>\n");
217        }
218
219        sb.append("</record>\n");
220        return sb.toString();
221    }
222
223    /**
224     * Return the header string for a set of XML formatted records.
225     *
226     * @param   h  The target handler (can be null)
227     * @return  a valid XML string
228     */
229    public String getHead(Handler h) {
230        StringBuffer sb = new StringBuffer();
231        String encoding;
232        sb.append("<?xml version=\"1.0\"");
233
234        if (h != null) {
235            encoding = h.getEncoding();
236        } else {
237            encoding = null;
238        }
239
240        if (encoding == null) {
241            // Figure out the default encoding.
242            encoding = java.nio.charset.Charset.defaultCharset().name();
243        }
244        // Try to map the encoding name to a canonical name.
245        try {
246            Charset cs = Charset.forName(encoding);
247            encoding = cs.name();
248        } catch (Exception ex) {
249            // We hit problems finding a canonical name.
250            // Just use the raw encoding name.
251        }
252
253        sb.append(" encoding=\"");
254        sb.append(encoding);
255        sb.append("\"");
256        sb.append(" standalone=\"no\"?>\n");
257        sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
258        sb.append("<log>\n");
259        return sb.toString();
260    }
261
262    /**
263     * Return the tail string for a set of XML formatted records.
264     *
265     * @param   h  The target handler (can be null)
266     * @return  a valid XML string
267     */
268    public String getTail(Handler h) {
269        return "</log>\n";
270    }
271}
272