1 /*
2  * Copyright (c) 2000, 2013, 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 
27 package java.util.logging;
28 
29 import java.io.*;
30 import java.nio.charset.Charset;
31 import 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 
46 public class XMLFormatter extends Formatter {
47     private LogManager manager = LogManager.getLogManager();
48 
49     // Append a two digit number.
a2(StringBuilder sb, int x)50     private void a2(StringBuilder 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
appendISO8601(StringBuilder sb, long millis)58     private void appendISO8601(StringBuilder sb, long millis) {
59         GregorianCalendar cal = new GregorianCalendar();
60         cal.setTimeInMillis(millis);
61         sb.append(cal.get(Calendar.YEAR));
62         sb.append('-');
63         a2(sb, cal.get(Calendar.MONTH) + 1);
64         sb.append('-');
65         a2(sb, cal.get(Calendar.DAY_OF_MONTH));
66         sb.append('T');
67         a2(sb, cal.get(Calendar.HOUR_OF_DAY));
68         sb.append(':');
69         a2(sb, cal.get(Calendar.MINUTE));
70         sb.append(':');
71         a2(sb, cal.get(Calendar.SECOND));
72     }
73 
74     // Append to the given StringBuilder an escaped version of the
75     // given text string where XML special characters have been escaped.
76     // For a null string we append "<null>"
escape(StringBuilder sb, String text)77     private void escape(StringBuilder sb, String text) {
78         if (text == null) {
79             text = "<null>";
80         }
81         for (int i = 0; i < text.length(); i++) {
82             char ch = text.charAt(i);
83             if (ch == '<') {
84                 sb.append("&lt;");
85             } else if (ch == '>') {
86                 sb.append("&gt;");
87             } else if (ch == '&') {
88                 sb.append("&amp;");
89             } else {
90                 sb.append(ch);
91             }
92         }
93     }
94 
95     /**
96      * Format the given message to XML.
97      * <p>
98      * This method can be overridden in a subclass.
99      * It is recommended to use the {@link Formatter#formatMessage}
100      * convenience method to localize and format the message field.
101      *
102      * @param record the log record to be formatted.
103      * @return a formatted log record
104      */
format(LogRecord record)105     public String format(LogRecord record) {
106         StringBuilder sb = new StringBuilder(500);
107         sb.append("<record>\n");
108 
109         sb.append("  <date>");
110         appendISO8601(sb, record.getMillis());
111         sb.append("</date>\n");
112 
113         sb.append("  <millis>");
114         sb.append(record.getMillis());
115         sb.append("</millis>\n");
116 
117         sb.append("  <sequence>");
118         sb.append(record.getSequenceNumber());
119         sb.append("</sequence>\n");
120 
121         String name = record.getLoggerName();
122         if (name != null) {
123             sb.append("  <logger>");
124             escape(sb, name);
125             sb.append("</logger>\n");
126         }
127 
128         sb.append("  <level>");
129         escape(sb, record.getLevel().toString());
130         sb.append("</level>\n");
131 
132         if (record.getSourceClassName() != null) {
133             sb.append("  <class>");
134             escape(sb, record.getSourceClassName());
135             sb.append("</class>\n");
136         }
137 
138         if (record.getSourceMethodName() != null) {
139             sb.append("  <method>");
140             escape(sb, record.getSourceMethodName());
141             sb.append("</method>\n");
142         }
143 
144         sb.append("  <thread>");
145         sb.append(record.getThreadID());
146         sb.append("</thread>\n");
147 
148         if (record.getMessage() != null) {
149             // Format the message string and its accompanying parameters.
150             String message = formatMessage(record);
151             sb.append("  <message>");
152             escape(sb, message);
153             sb.append("</message>");
154             sb.append("\n");
155         } else {
156             sb.append("<message/>");
157             sb.append("\n");
158         }
159 
160         // If the message is being localized, output the key, resource
161         // bundle name, and params.
162         ResourceBundle bundle = record.getResourceBundle();
163         try {
164             if (bundle != null && bundle.getString(record.getMessage()) != null) {
165                 sb.append("  <key>");
166                 escape(sb, record.getMessage());
167                 sb.append("</key>\n");
168                 sb.append("  <catalog>");
169                 escape(sb, record.getResourceBundleName());
170                 sb.append("</catalog>\n");
171             }
172         } catch (Exception ex) {
173             // The message is not in the catalog.  Drop through.
174         }
175 
176         Object parameters[] = record.getParameters();
177         //  Check to see if the parameter was not a messagetext format
178         //  or was not null or empty
179         if ( parameters != null && parameters.length != 0
180                 && record.getMessage().indexOf("{") == -1 ) {
181             for (int i = 0; i < parameters.length; i++) {
182                 sb.append("  <param>");
183                 try {
184                     escape(sb, parameters[i].toString());
185                 } catch (Exception ex) {
186                     sb.append("???");
187                 }
188                 sb.append("</param>\n");
189             }
190         }
191 
192         if (record.getThrown() != null) {
193             // Report on the state of the throwable.
194             Throwable th = record.getThrown();
195             sb.append("  <exception>\n");
196             sb.append("    <message>");
197             escape(sb, th.toString());
198             sb.append("</message>\n");
199             StackTraceElement trace[] = th.getStackTrace();
200             for (int i = 0; i < trace.length; i++) {
201                 StackTraceElement frame = trace[i];
202                 sb.append("    <frame>\n");
203                 sb.append("      <class>");
204                 escape(sb, frame.getClassName());
205                 sb.append("</class>\n");
206                 sb.append("      <method>");
207                 escape(sb, frame.getMethodName());
208                 sb.append("</method>\n");
209                 // Check for a line number.
210                 if (frame.getLineNumber() >= 0) {
211                     sb.append("      <line>");
212                     sb.append(frame.getLineNumber());
213                     sb.append("</line>\n");
214                 }
215                 sb.append("    </frame>\n");
216             }
217             sb.append("  </exception>\n");
218         }
219 
220         sb.append("</record>\n");
221         return sb.toString();
222     }
223 
224     /**
225      * Return the header string for a set of XML formatted records.
226      *
227      * @param   h  The target handler (can be null)
228      * @return  a valid XML string
229      */
getHead(Handler h)230     public String getHead(Handler h) {
231         StringBuilder sb = new StringBuilder();
232         String encoding;
233         sb.append("<?xml version=\"1.0\"");
234 
235         if (h != null) {
236             encoding = h.getEncoding();
237         } else {
238             encoding = null;
239         }
240 
241         if (encoding == null) {
242             // Figure out the default encoding.
243             encoding = java.nio.charset.Charset.defaultCharset().name();
244         }
245         // Try to map the encoding name to a canonical name.
246         try {
247             Charset cs = Charset.forName(encoding);
248             encoding = cs.name();
249         } catch (Exception ex) {
250             // We hit problems finding a canonical name.
251             // Just use the raw encoding name.
252         }
253 
254         sb.append(" encoding=\"");
255         sb.append(encoding);
256         sb.append("\"");
257         sb.append(" standalone=\"no\"?>\n");
258         sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
259         sb.append("<log>\n");
260         return sb.toString();
261     }
262 
263     /**
264      * Return the tail string for a set of XML formatted records.
265      *
266      * @param   h  The target handler (can be null)
267      * @return  a valid XML string
268      */
getTail(Handler h)269     public String getTail(Handler h) {
270         return "</log>\n";
271     }
272 }
273