View Javadoc
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.spi.ILoggingEvent;
21  import ch.qos.logback.classic.spi.IThrowableProxy;
22  import ch.qos.logback.classic.spi.StackTraceElementProxy;
23  import ch.qos.logback.core.CoreConstants;
24  import ch.qos.logback.core.LayoutBase;
25  import ch.qos.logback.core.helpers.Transform;
26  
27  // Code is based on revision 309623 of org.apache.log4j.xml.XMLLayout dated "Wed
28  // Jul 31 09:25:14 2002 UTC" as authored by Ceki Gulcu.
29  // See also http://tinyurl.com/dch9mr
30  
31  /**
32   * 
33   * Generates log4j.dtd compliant XML documents.
34   * 
35   * 
36   * @author Ceki Gülcü
37   */
38  public class XMLLayout extends LayoutBase<ILoggingEvent> {
39  
40      private final int DEFAULT_SIZE = 256;
41      private final int UPPER_LIMIT = 2048;
42  
43      private StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
44      private boolean locationInfo = false;
45      private boolean properties = false;
46  
47      @Override
48      public void start() {
49          super.start();
50      }
51  
52      /**
53       * The <b>LocationInfo</b> option takes a boolean value. By default, it is set
54       * to false which means there will be no location information output by this
55       * layout. If the option is set to true, then the file name and line number
56       * of the statement at the origin of the log statement will be output.
57       * 
58       * <p>
59       * If you are embedding this layout within an
60       * {@link org.apache.log4j.net.SMTPAppender} then make sure to set the
61       * <b>LocationInfo</b> option of that appender as well.
62       */
63      public void setLocationInfo(boolean flag) {
64          locationInfo = flag;
65      }
66  
67      /**
68       * Returns the current value of the <b>LocationInfo</b> option.
69       */
70      public boolean getLocationInfo() {
71          return locationInfo;
72      }
73  
74      /**
75       * Sets whether MDC key-value pairs should be output, default false.
76       * 
77       * @param flag new value.
78       * @since 1.2.15
79       */
80      public void setProperties(final boolean flag) {
81          properties = flag;
82      }
83  
84      /**
85       * Gets whether MDC key-value pairs should be output.
86       * 
87       * @return true if MDC key-value pairs are output.
88       * @since 1.2.15
89       */
90      public boolean getProperties() {
91          return properties;
92      }
93  
94      /**
95       * Formats a {@link ILoggingEvent} in conformity with the log4j.dtd.
96       */
97      public String doLayout(ILoggingEvent event) {
98  
99          // 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 }