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}