1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.log4j;
15  
16  import java.util.Map;
17  import java.util.Set;
18  import java.util.Map.Entry;
19  
20  import ch.qos.logback.classic.net.SMTPAppender;
21  import ch.qos.logback.classic.spi.ILoggingEvent;
22  import ch.qos.logback.classic.spi.IThrowableProxy;
23  import ch.qos.logback.classic.spi.StackTraceElementProxy;
24  import ch.qos.logback.core.CoreConstants;
25  import ch.qos.logback.core.LayoutBase;
26  import ch.qos.logback.core.helpers.Transform;
27  
28  // Code is based on revision 309623 of org.apache.log4j.xml.XMLLayout dated "Wed
29  // Jul 31 09:25:14 2002 UTC" as authored by Ceki Gulcu.
30  // See also http://tinyurl.com/dch9mr
31  
32  /**
33   * 
34   * Generates log4j.dtd compliant XML documents.
35   * 
36   * 
37   * @author Ceki Gülcü
38   */
39  public class XMLLayout extends LayoutBase<ILoggingEvent> {
40  
41      private static final int DEFAULT_SIZE = 256;
42  
43      private boolean locationInfo = false;
44      private boolean properties = false;
45  
46      @Override
47      public void start() {
48          super.start();
49      }
50  
51      /**
52       * The <b>LocationInfo</b> option takes a boolean value. By default, it is set
53       * to false which means there will be no location information output by this
54       * layout. If the option is set to true, then the file name and line number
55       * of the statement at the origin of the log statement will be output.
56       * 
57       * <p>
58       * If you are embedding this layout within an
59       * {@link SMTPAppender} then make sure to set the
60       * {@link SMTPAppender#setIncludeCallerData(boolean) includeCallerData} option
61       * as well.
62       * </p>
63       */
64      public void setLocationInfo(boolean flag) {
65          locationInfo = flag;
66      }
67  
68      /**
69       * Returns the current value of the <b>LocationInfo</b> option.
70       */
71      public boolean getLocationInfo() {
72          return locationInfo;
73      }
74  
75      /**
76       * Sets whether MDC key-value pairs should be output, default false.
77       * 
78       * @param flag new value.
79       * @since 1.2.15
80       */
81      public void setProperties(final boolean flag) {
82          properties = flag;
83      }
84  
85      /**
86       * Gets whether MDC key-value pairs should be output.
87       * 
88       * @return true if MDC key-value pairs are output.
89       * @since 1.2.15
90       */
91      public boolean getProperties() {
92          return properties;
93      }
94  
95      /**
96       * Formats a {@link ILoggingEvent} in conformity with the log4j.dtd.
97       */
98      public String doLayout(ILoggingEvent event) {
99  
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 }