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.net.SMTPAppender;
021import ch.qos.logback.classic.spi.ILoggingEvent;
022import ch.qos.logback.classic.spi.IThrowableProxy;
023import ch.qos.logback.classic.spi.StackTraceElementProxy;
024import ch.qos.logback.core.CoreConstants;
025import ch.qos.logback.core.LayoutBase;
026import ch.qos.logback.core.helpers.Transform;
027
028// Code is based on revision 309623 of org.apache.log4j.xml.XMLLayout dated "Wed
029// Jul 31 09:25:14 2002 UTC" as authored by Ceki Gulcu.
030// See also http://tinyurl.com/dch9mr
031
032/**
033 * 
034 * Generates log4j.dtd compliant XML documents.
035 * 
036 * 
037 * @author Ceki Gülcü
038 */
039public class XMLLayout extends LayoutBase<ILoggingEvent> {
040
041    private static final int DEFAULT_SIZE = 256;
042
043    private boolean locationInfo = false;
044    private boolean properties = false;
045
046    @Override
047    public void start() {
048        super.start();
049    }
050
051    /**
052     * The <b>LocationInfo</b> option takes a boolean value. By default, it is set
053     * to false which means there will be no location information output by this
054     * layout. If the option is set to true, then the file name and line number
055     * of the statement at the origin of the log statement will be output.
056     * 
057     * <p>
058     * If you are embedding this layout within an
059     * {@link SMTPAppender} then make sure to set the
060     * {@link SMTPAppender#setIncludeCallerData(boolean) includeCallerData} option
061     * as well.
062     * </p>
063     */
064    public void setLocationInfo(boolean flag) {
065        locationInfo = flag;
066    }
067
068    /**
069     * Returns the current value of the <b>LocationInfo</b> option.
070     */
071    public boolean getLocationInfo() {
072        return locationInfo;
073    }
074
075    /**
076     * Sets whether MDC key-value pairs should be output, default false.
077     * 
078     * @param flag new value.
079     * @since 1.2.15
080     */
081    public void setProperties(final boolean flag) {
082        properties = flag;
083    }
084
085    /**
086     * Gets whether MDC key-value pairs should be output.
087     * 
088     * @return true if MDC key-value pairs are output.
089     * @since 1.2.15
090     */
091    public boolean getProperties() {
092        return properties;
093    }
094
095    /**
096     * Formats a {@link ILoggingEvent} in conformity with the log4j.dtd.
097     */
098    public String doLayout(ILoggingEvent event) {
099
100        StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
101
102        // We yield to the \r\n heresy.
103
104        buf.append("<log4j:event logger=\"");
105        buf.append(Transform.escapeTags(event.getLoggerName()));
106        buf.append("\"\r\n");
107        buf.append("             timestamp=\"");
108        buf.append(event.getTimeStamp());
109        buf.append("\" level=\"");
110        buf.append(event.getLevel());
111        buf.append("\" thread=\"");
112        buf.append(Transform.escapeTags(event.getThreadName()));
113        buf.append("\">\r\n");
114
115        buf.append("  <log4j:message>");
116        buf.append(Transform.escapeTags(event.getFormattedMessage()));
117        buf.append("</log4j:message>\r\n");
118
119        // logback does not support NDC
120        // String ndc = event.getNDC();
121
122        IThrowableProxy tp = event.getThrowableProxy();
123        if (tp != null) {
124            StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
125            buf.append("  <log4j:throwable><![CDATA[");
126            for (StackTraceElementProxy step : stepArray) {
127                buf.append(CoreConstants.TAB);
128                buf.append(step.toString());
129                buf.append("\r\n");
130            }
131            buf.append("]]></log4j:throwable>\r\n");
132        }
133
134        if (locationInfo) {
135            StackTraceElement[] callerDataArray = event.getCallerData();
136            if (callerDataArray != null && callerDataArray.length > 0) {
137                StackTraceElement immediateCallerData = callerDataArray[0];
138                buf.append("  <log4j:locationInfo class=\"");
139                buf.append(immediateCallerData.getClassName());
140                buf.append("\"\r\n");
141                buf.append("                      method=\"");
142                buf.append(Transform.escapeTags(immediateCallerData.getMethodName()));
143                buf.append("\" file=\"");
144                buf.append(Transform.escapeTags(immediateCallerData.getFileName()));
145                buf.append("\" line=\"");
146                buf.append(immediateCallerData.getLineNumber());
147                buf.append("\"/>\r\n");
148            }
149        }
150
151        /*
152         * <log4j:properties> <log4j:data name="name" value="value"/>
153         * </log4j:properties>
154         */
155        if (this.getProperties()) {
156            Map<String, String> propertyMap = event.getMDCPropertyMap();
157
158            if ((propertyMap != null) && (propertyMap.size() != 0)) {
159                Set<Entry<String, String>> entrySet = propertyMap.entrySet();
160                buf.append("  <log4j:properties>");
161                for (Entry<String, String> entry : entrySet) {
162                    buf.append("\r\n    <log4j:data");
163                    buf.append(" name='" + Transform.escapeTags(entry.getKey()) + "'");
164                    buf.append(" value='" + Transform.escapeTags(entry.getValue()) + "'");
165                    buf.append(" />");
166                }
167                buf.append("\r\n  </log4j:properties>");
168            }
169        }
170
171        buf.append("\r\n</log4j:event>\r\n\r\n");
172
173        return buf.toString();
174    }
175
176    @Override
177    public String getContentType() {
178        return "text/xml";
179    }
180
181}