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 <?xml version="1.0" ?> 043 044 <!DOCTYPE log4j:eventSet PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd" [<!ENTITY data SYSTEM "abc">]> 045 046 <log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/"> 047 &data; 048 </log4j:eventSet> 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ülcü 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}