001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018// Contributors:   Mathias Bogaert
019
020package org.apache.log4j.xml;
021
022import org.apache.log4j.Layout;
023import org.apache.log4j.helpers.Transform;
024import org.apache.log4j.spi.LocationInfo;
025import org.apache.log4j.spi.LoggingEvent;
026
027import java.util.Set;
028import java.util.Arrays;
029
030/**
031 * The output of the XMLLayout consists of a series of log4j:event
032 * elements as defined in the <a
033 * href="log4j.dtd">log4j.dtd</a>. It does not output a
034 * complete well-formed XML file. The output is designed to be
035 * included as an <em>external entity</em> in a separate file to form
036 * a correct XML file.
037 *
038 * <p>For example, if <code>abc</code> is the name of the file where
039 * the XMLLayout ouput goes, then a well-formed XML file would be:
040 *
041  <pre>
042   &lt;?xml version="1.0" ?&gt;
043 
044  &lt;!DOCTYPE log4j:eventSet PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd" [&lt;!ENTITY data SYSTEM "abc"&gt;]&gt;
045 
046  &lt;log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;
047        &nbsp;&nbsp;&data;
048  &lt;/log4j:eventSet&gt;
049  </pre>
050 
051 * <p>This approach enforces the independence of the XMLLayout and the
052 * appender where it is embedded.
053 *
054 * <p>The <code>version</code> attribute helps components to correctly
055 * intrepret output generated by XMLLayout. The value of this
056 * attribute should be "1.1" for output generated by log4j versions
057 * prior to log4j 1.2 (final release) and "1.2" for relase 1.2 and
058 * later.
059 *
060 * Appenders using this layout should have their encoding
061 * set to UTF-8 or UTF-16, otherwise events containing
062 * non ASCII characters could result in corrupted
063 * log files. 
064 *
065 * @author Ceki  G&uuml;lc&uuml;
066 * @since 0.9.0 
067 * */
068public class XMLLayout extends Layout {
069
070  private  final int DEFAULT_SIZE = 256;
071  private final int UPPER_LIMIT = 2048;
072
073  private StringBuffer buf = new StringBuffer(DEFAULT_SIZE);
074  private boolean locationInfo = false;
075  private boolean properties = false;
076 
077  /**
078   * The <b>LocationInfo</b> option takes a boolean value. By default,
079   * it is set to false which means there will be no location
080   * information output by this layout. If the the option is set to
081   * true, then the file name and line number of the statement at the
082   * origin of the log statement will be output.
083   *
084   * <p>If you are embedding this layout within an {@link
085   * org.apache.log4j.net.SMTPAppender} then make sure to set the
086   * <b>LocationInfo</b> option of that appender as well.
087   * */
088  public void setLocationInfo(boolean flag) {
089    locationInfo = flag;
090  }
091  
092  /**
093     Returns the current value of the <b>LocationInfo</b> option.
094   */
095  public boolean getLocationInfo() {
096    return locationInfo;
097  }
098
099    /**
100     * Sets whether MDC key-value pairs should be output, default false.
101     * @param flag new value.
102     * @since 1.2.15
103     */
104  public void setProperties(final boolean flag) {
105      properties = flag;
106  }
107
108    /**
109     * Gets whether MDC key-value pairs should be output.
110     * @return true if MDC key-value pairs are output.
111     * @since 1.2.15
112     */
113  public boolean getProperties() {
114      return properties;
115  }
116
117  /** No options to activate. */
118  public void activateOptions() {
119  }
120
121
122  /**
123   * Formats a {@link org.apache.log4j.spi.LoggingEvent} in conformance with the log4j.dtd.
124   * */
125  public String format(final LoggingEvent event) {
126
127    // Reset working buffer. If the buffer is too large, then we need a new
128    // one in order to avoid the penalty of creating a large array.
129    if(buf.capacity() > UPPER_LIMIT) {
130      buf = new StringBuffer(DEFAULT_SIZE);
131    } else {
132      buf.setLength(0);
133    }
134    
135    // We yield to the \r\n heresy.
136
137    buf.append("<log4j:event logger=\"");
138    buf.append(Transform.escapeTags(event.getLoggerName()));
139    buf.append("\" timestamp=\"");
140    buf.append(event.timeStamp);
141    buf.append("\" level=\"");
142    buf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
143    buf.append("\" thread=\"");
144    buf.append(Transform.escapeTags(event.getThreadName()));
145    buf.append("\">\r\n");
146
147    buf.append("<log4j:message><![CDATA[");
148    // Append the rendered message. Also make sure to escape any
149    // existing CDATA sections.
150    Transform.appendEscapingCDATA(buf, event.getRenderedMessage());
151    buf.append("]]></log4j:message>\r\n");       
152    
153    String ndc = event.getNDC();
154    if(ndc != null) {
155      buf.append("<log4j:NDC><![CDATA[");
156      Transform.appendEscapingCDATA(buf, ndc);
157      buf.append("]]></log4j:NDC>\r\n");       
158    }
159    
160    String[] s = event.getThrowableStrRep();
161    if(s != null) {
162      buf.append("<log4j:throwable><![CDATA[");
163      for(int i = 0; i < s.length; i++) {
164          Transform.appendEscapingCDATA(buf, s[i]);
165              buf.append("\r\n");
166      }
167      buf.append("]]></log4j:throwable>\r\n");
168    }
169    
170    if(locationInfo) { 
171      LocationInfo locationInfo = event.getLocationInformation();       
172      buf.append("<log4j:locationInfo class=\"");
173      buf.append(Transform.escapeTags(locationInfo.getClassName()));
174      buf.append("\" method=\"");
175      buf.append(Transform.escapeTags(locationInfo.getMethodName()));
176      buf.append("\" file=\"");
177      buf.append(Transform.escapeTags(locationInfo.getFileName()));
178      buf.append("\" line=\"");
179      buf.append(locationInfo.getLineNumber());
180      buf.append("\"/>\r\n");
181    }
182
183    if (properties) {
184        Set keySet = event.getPropertyKeySet();
185        if (keySet.size() > 0) {
186            buf.append("<log4j:properties>\r\n");
187            Object[] keys = keySet.toArray();
188            Arrays.sort(keys);
189            for (int i = 0; i < keys.length; i++) {
190                String key = keys[i].toString();
191                Object val = event.getMDC(key);
192                if (val != null) {
193                    buf.append("<log4j:data name=\"");
194                    buf.append(Transform.escapeTags(key));
195                    buf.append("\" value=\"");
196                    buf.append(Transform.escapeTags(String.valueOf(val)));
197                    buf.append("\"/>\r\n");
198                }
199            }
200            buf.append("</log4j:properties>\r\n");
201        }
202    }
203    
204    buf.append("</log4j:event>\r\n\r\n");
205    
206    return buf.toString();
207  }
208  
209  /**
210     The XMLLayout prints and does not ignore exceptions. Hence the
211     return value <code>false</code>.
212  */
213  public boolean ignoresThrowable() {
214    return false;
215  }
216}