View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.log4j.layout;
18  
19  import java.nio.charset.StandardCharsets;
20  import java.util.List;
21  
22  import org.apache.logging.log4j.core.Layout;
23  import org.apache.logging.log4j.core.LogEvent;
24  import org.apache.logging.log4j.core.config.Node;
25  import org.apache.logging.log4j.core.config.plugins.Plugin;
26  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
27  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
28  import org.apache.logging.log4j.core.layout.AbstractStringLayout;
29  import org.apache.logging.log4j.core.layout.ByteBufferDestination;
30  import org.apache.logging.log4j.core.util.Transform;
31  import org.apache.logging.log4j.util.BiConsumer;
32  import org.apache.logging.log4j.util.ReadOnlyStringMap;
33  import org.apache.logging.log4j.util.Strings;
34  
35  /**
36   * Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations.
37   *
38   * Originally developed by Ceki Gülcü, Mathias Bogaert.
39   */
40  @Plugin(name = "Log4j1XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
41  public final class Log4j1XmlLayout extends AbstractStringLayout {
42  
43      private final boolean locationInfo;
44      private final boolean properties;
45  
46      @PluginFactory
47      public static Log4j1XmlLayout createLayout(
48              // @formatter:off
49              @PluginAttribute(value = "locationInfo") final boolean locationInfo,
50              @PluginAttribute(value = "properties") final boolean properties
51              // @formatter:on
52      ) {
53          return new Log4j1XmlLayout(locationInfo, properties);
54      }
55  
56      private Log4j1XmlLayout(final boolean locationInfo, final boolean properties) {
57          super(StandardCharsets.UTF_8);
58          this.locationInfo = locationInfo;
59          this.properties = properties;
60      }
61  
62      public boolean isLocationInfo() {
63          return locationInfo;
64      }
65  
66      public boolean isProperties() {
67          return properties;
68      }
69  
70      @Override
71      public void encode(final LogEvent event, final ByteBufferDestination destination) {
72          final StringBuilder text = getStringBuilder();
73          formatTo(event, text);
74          getStringBuilderEncoder().encode(text, destination);
75      }
76  
77      @Override
78      public String toSerializable(final LogEvent event) {
79          final StringBuilder text = getStringBuilder();
80          formatTo(event, text);
81          return text.toString();
82      }
83  
84      private void formatTo(final LogEvent event, final StringBuilder buf) {
85          // We yield to the \r\n heresy.
86  
87          buf.append("<log4j:event logger=\"");
88          buf.append(Transform.escapeHtmlTags(event.getLoggerName()));
89          buf.append("\" timestamp=\"");
90          buf.append(event.getTimeMillis());
91          buf.append("\" level=\"");
92          buf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
93          buf.append("\" thread=\"");
94          buf.append(Transform.escapeHtmlTags(event.getThreadName()));
95          buf.append("\">\r\n");
96  
97          buf.append("<log4j:message><![CDATA[");
98          // Append the rendered message. Also make sure to escape any existing CDATA sections.
99          Transform.appendEscapingCData(buf, event.getMessage().getFormattedMessage());
100         buf.append("]]></log4j:message>\r\n");
101 
102         final List<String> ndc = event.getContextStack().asList();
103         if (!ndc.isEmpty()) {
104             buf.append("<log4j:NDC><![CDATA[");
105             Transform.appendEscapingCData(buf, Strings.join(ndc, ' '));
106             buf.append("]]></log4j:NDC>\r\n");
107         }
108 
109         @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
110 		final
111         Throwable thrown = event.getThrown();
112         if (thrown != null) {
113             buf.append("<log4j:throwable><![CDATA[");
114             buf.append(thrown.toString());
115             buf.append("\r\n");
116             for (final StackTraceElement element : thrown.getStackTrace()) {
117                 Transform.appendEscapingCData(buf, "\tat " + element.toString());
118                 buf.append("\r\n");
119             }
120             buf.append("]]></log4j:throwable>\r\n");
121         }
122 
123         if (locationInfo) {
124             final StackTraceElement source = event.getSource();
125             if (source != null) {
126                 buf.append("<log4j:locationInfo class=\"");
127                 buf.append(Transform.escapeHtmlTags(source.getClassName()));
128                 buf.append("\" method=\"");
129                 buf.append(Transform.escapeHtmlTags(source.getMethodName()));
130                 buf.append("\" file=\"");
131                 buf.append(Transform.escapeHtmlTags(source.getFileName()));
132                 buf.append("\" line=\"");
133                 buf.append(source.getLineNumber());
134                 buf.append("\"/>\r\n");
135             }
136         }
137 
138         if (properties) {
139             final ReadOnlyStringMap contextMap = event.getContextData();
140             if (!contextMap.isEmpty()) {
141                 buf.append("<log4j:properties>\r\n");
142                 contextMap.forEach(new BiConsumer<String, String>() {
143                     @Override
144                     public void accept(final String key, final String val) {
145                         if (val != null) {
146                             buf.append("<log4j:data name=\"");
147                             buf.append(Transform.escapeHtmlTags(key));
148                             buf.append("\" value=\"");
149                             buf.append(Transform.escapeHtmlTags(val));
150                             buf.append("\"/>\r\n");
151                         }
152                     }
153                 });
154                 buf.append("</log4j:properties>\r\n");
155             }
156         }
157 
158         buf.append("</log4j:event>\r\n\r\n");
159     }
160 
161 }