001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.log4j; 015 016import java.util.Map; 017import java.util.Set; 018import java.util.Map.Entry; 019 020import ch.qos.logback.classic.spi.ILoggingEvent; 021import ch.qos.logback.classic.spi.IThrowableProxy; 022import ch.qos.logback.classic.spi.StackTraceElementProxy; 023import ch.qos.logback.core.CoreConstants; 024import ch.qos.logback.core.LayoutBase; 025import ch.qos.logback.core.helpers.Transform; 026 027// Code is based on revision 309623 of org.apache.log4j.xml.XMLLayout dated "Wed 028// Jul 31 09:25:14 2002 UTC" as authored by Ceki Gulcu. 029// See also http://tinyurl.com/dch9mr 030 031/** 032 * 033 * Generates log4j.dtd compliant XML documents. 034 * 035 * 036 * @author Ceki Gülcü 037 */ 038public class XMLLayout extends LayoutBase<ILoggingEvent> { 039 040 private final int DEFAULT_SIZE = 256; 041 private final int UPPER_LIMIT = 2048; 042 043 private StringBuilder buf = new StringBuilder(DEFAULT_SIZE); 044 private boolean locationInfo = false; 045 private boolean properties = false; 046 047 @Override 048 public void start() { 049 super.start(); 050 } 051 052 /** 053 * The <b>LocationInfo</b> option takes a boolean value. By default, it is set 054 * to false which means there will be no location information output by this 055 * layout. If the option is set to true, then the file name and line number 056 * of the statement at the origin of the log statement will be output. 057 * 058 * <p> 059 * If you are embedding this layout within an 060 * {@link org.apache.log4j.net.SMTPAppender} then make sure to set the 061 * <b>LocationInfo</b> option of that appender as well. 062 */ 063 public void setLocationInfo(boolean flag) { 064 locationInfo = flag; 065 } 066 067 /** 068 * Returns the current value of the <b>LocationInfo</b> option. 069 */ 070 public boolean getLocationInfo() { 071 return locationInfo; 072 } 073 074 /** 075 * Sets whether MDC key-value pairs should be output, default false. 076 * 077 * @param flag new value. 078 * @since 1.2.15 079 */ 080 public void setProperties(final boolean flag) { 081 properties = flag; 082 } 083 084 /** 085 * Gets whether MDC key-value pairs should be output. 086 * 087 * @return true if MDC key-value pairs are output. 088 * @since 1.2.15 089 */ 090 public boolean getProperties() { 091 return properties; 092 } 093 094 /** 095 * Formats a {@link ILoggingEvent} in conformity with the log4j.dtd. 096 */ 097 public String doLayout(ILoggingEvent event) { 098 099 // Reset working buffer. If the buffer is too large, then we need a new 100 // one in order to avoid the penalty of creating a large array. 101 if (buf.capacity() > UPPER_LIMIT) { 102 buf = new StringBuilder(DEFAULT_SIZE); 103 } else { 104 buf.setLength(0); 105 } 106 107 // We yield to the \r\n heresy. 108 109 buf.append("<log4j:event logger=\""); 110 buf.append(Transform.escapeTags(event.getLoggerName())); 111 buf.append("\"\r\n"); 112 buf.append(" timestamp=\""); 113 buf.append(event.getTimeStamp()); 114 buf.append("\" level=\""); 115 buf.append(event.getLevel()); 116 buf.append("\" thread=\""); 117 buf.append(Transform.escapeTags(event.getThreadName())); 118 buf.append("\">\r\n"); 119 120 buf.append(" <log4j:message>"); 121 buf.append(Transform.escapeTags(event.getFormattedMessage())); 122 buf.append("</log4j:message>\r\n"); 123 124 // logback does not support NDC 125 // String ndc = event.getNDC(); 126 127 IThrowableProxy tp = event.getThrowableProxy(); 128 if (tp != null) { 129 StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray(); 130 buf.append(" <log4j:throwable><![CDATA["); 131 for (StackTraceElementProxy step : stepArray) { 132 buf.append(CoreConstants.TAB); 133 buf.append(step.toString()); 134 buf.append("\r\n"); 135 } 136 buf.append("]]></log4j:throwable>\r\n"); 137 } 138 139 if (locationInfo) { 140 StackTraceElement[] callerDataArray = event.getCallerData(); 141 if (callerDataArray != null && callerDataArray.length > 0) { 142 StackTraceElement immediateCallerData = callerDataArray[0]; 143 buf.append(" <log4j:locationInfo class=\""); 144 buf.append(immediateCallerData.getClassName()); 145 buf.append("\"\r\n"); 146 buf.append(" method=\""); 147 buf.append(Transform.escapeTags(immediateCallerData.getMethodName())); 148 buf.append("\" file=\""); 149 buf.append(Transform.escapeTags(immediateCallerData.getFileName())); 150 buf.append("\" line=\""); 151 buf.append(immediateCallerData.getLineNumber()); 152 buf.append("\"/>\r\n"); 153 } 154 } 155 156 /* 157 * <log4j:properties> <log4j:data name="name" value="value"/> 158 * </log4j:properties> 159 */ 160 if (this.getProperties()) { 161 Map<String, String> propertyMap = event.getMDCPropertyMap(); 162 163 if ((propertyMap != null) && (propertyMap.size() != 0)) { 164 Set<Entry<String, String>> entrySet = propertyMap.entrySet(); 165 buf.append(" <log4j:properties>"); 166 for (Entry<String, String> entry : entrySet) { 167 buf.append("\r\n <log4j:data"); 168 buf.append(" name='" + Transform.escapeTags(entry.getKey()) + "'"); 169 buf.append(" value='" + Transform.escapeTags(entry.getValue()) + "'"); 170 buf.append(" />"); 171 } 172 buf.append("\r\n </log4j:properties>"); 173 } 174 } 175 176 buf.append("\r\n</log4j:event>\r\n\r\n"); 177 178 return buf.toString(); 179 } 180 181 @Override 182 public String getContentType() { 183 return "text/xml"; 184 } 185 186}