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         // Android-added: Include empty <message/> tag. http://b/25861348#comment17
156         } else {
157             sb.append("<message/>");
158             sb.append("\n");
159         }
160 
161         // If the message is being localized, output the key, resource
162         // bundle name, and params.
163         ResourceBundle bundle = record.getResourceBundle();
164         try {
165             if (bundle != null && bundle.getString(record.getMessage()) != null) {
166                 sb.append("  <key>");
167                 escape(sb, record.getMessage());
168                 sb.append("</key>\n");
169                 sb.append("  <catalog>");
170                 escape(sb, record.getResourceBundleName());
171                 sb.append("</catalog>\n");
172             }
173         } catch (Exception ex) {
174             // The message is not in the catalog.  Drop through.
175         }
176 
177         Object parameters[] = record.getParameters();
178         //  Check to see if the parameter was not a messagetext format
179         //  or was not null or empty
180         if ( parameters != null && parameters.length != 0
181                 && record.getMessage().indexOf("{") == -1 ) {
182             for (int i = 0; i < parameters.length; i++) {
183                 sb.append("  <param>");
184                 try {
185                     escape(sb, parameters[i].toString());
186                 } catch (Exception ex) {
187                     sb.append("???");
188                 }
189                 sb.append("</param>\n");
190             }
191         }
192 
193         if (record.getThrown() != null) {
194             // Report on the state of the throwable.
195             Throwable th = record.getThrown();
196             sb.append("  <exception>\n");
197             sb.append("    <message>");
198             escape(sb, th.toString());
199             sb.append("</message>\n");
200             StackTraceElement trace[] = th.getStackTrace();
201             for (int i = 0; i < trace.length; i++) {
202                 StackTraceElement frame = trace[i];
203                 sb.append("    <frame>\n");
204                 sb.append("      <class>");
205                 escape(sb, frame.getClassName());
206                 sb.append("</class>\n");
207                 sb.append("      <method>");
208                 escape(sb, frame.getMethodName());
209                 sb.append("</method>\n");
210                 // Check for a line number.
211                 if (frame.getLineNumber() >= 0) {
212                     sb.append("      <line>");
213                     sb.append(frame.getLineNumber());
214                     sb.append("</line>\n");
215                 }
216                 sb.append("    </frame>\n");
217             }
218             sb.append("  </exception>\n");
219         }
220 
221         sb.append("</record>\n");
222         return sb.toString();
223     }
224 
225     /**
226      * Return the header string for a set of XML formatted records.
227      *
228      * @param   h  The target handler (can be null)
229      * @return  a valid XML string
230      */
getHead(Handler h)231     public String getHead(Handler h) {
232         StringBuilder sb = new StringBuilder();
233         String encoding;
234         sb.append("<?xml version=\"1.0\"");
235 
236         if (h != null) {
237             encoding = h.getEncoding();
238         } else {
239             encoding = null;
240         }
241 
242         if (encoding == null) {
243             // Figure out the default encoding.
244             encoding = java.nio.charset.Charset.defaultCharset().name();
245         }
246         // Try to map the encoding name to a canonical name.
247         try {
248             Charset cs = Charset.forName(encoding);
249             encoding = cs.name();
250         } catch (Exception ex) {
251             // We hit problems finding a canonical name.
252             // Just use the raw encoding name.
253         }
254 
255         sb.append(" encoding=\"");
256         sb.append(encoding);
257         sb.append("\"");
258         sb.append(" standalone=\"no\"?>\n");
259         sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
260         sb.append("<log>\n");
261         return sb.toString();
262     }
263 
264     /**
265      * Return the tail string for a set of XML formatted records.
266      *
267      * @param   h  The target handler (can be null)
268      * @return  a valid XML string
269      */
getTail(Handler h)270     public String getTail(Handler h) {
271         return "</log>\n";
272     }
273 }
274