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 
  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.
  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
  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>"
  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      */
 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         }
 156 
 157         // If the message is being localized, output the key, resource
 158         // bundle name, and params.
 159         ResourceBundle bundle = record.getResourceBundle();
 160         try {
 161             if (bundle != null && 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         StringBuilder sb = new StringBuilder();
 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 }