1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.nio.charset.Charset;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.apache.logging.log4j.Marker;
25 import org.apache.logging.log4j.core.LogEvent;
26 import org.apache.logging.log4j.core.config.plugins.Plugin;
27 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
28 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
29 import org.apache.logging.log4j.core.helpers.Charsets;
30 import org.apache.logging.log4j.core.helpers.Strings;
31 import org.apache.logging.log4j.core.helpers.Throwables;
32 import org.apache.logging.log4j.core.helpers.Transform;
33 import org.apache.logging.log4j.message.Message;
34 import org.apache.logging.log4j.message.MultiformatMessage;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 @Plugin(name = "XMLLayout", category = "Core", elementType = "layout", printObject = true)
81 public class XMLLayout extends AbstractStringLayout {
82
83 private static final String XML_NAMESPACE = "http://logging.apache.org/log4j/2.0/events";
84 private static final String ROOT_TAG = "Events";
85 private static final int DEFAULT_SIZE = 256;
86
87
88 private static final String DEFAULT_EOL = "\r\n";
89 private static final String COMPACT_EOL = "";
90 private static final String DEFAULT_INDENT = " ";
91 private static final String COMPACT_INDENT = "";
92 private static final String DEFAULT_NS_PREFIX = "log4j";
93
94 private static final String[] FORMATS = new String[] {"xml"};
95
96 private final boolean locationInfo;
97 private final boolean properties;
98 private final boolean complete;
99 private final String namespacePrefix;
100 private final String eol;
101 private final String indent1;
102 private final String indent2;
103 private final String indent3;
104
105 protected XMLLayout(final boolean locationInfo, final boolean properties, final boolean complete,
106 boolean compact, final String nsPrefix, final Charset charset) {
107 super(charset);
108 this.locationInfo = locationInfo;
109 this.properties = properties;
110 this.complete = complete;
111 this.eol = compact ? COMPACT_EOL : DEFAULT_EOL;
112 this.indent1 = compact ? COMPACT_INDENT : DEFAULT_INDENT;
113 this.indent2 = this.indent1 + this.indent1;
114 this.indent3 = this.indent2 + this.indent1;
115 this.namespacePrefix = (Strings.isEmpty(nsPrefix) ? DEFAULT_NS_PREFIX : nsPrefix) + ":";
116 }
117
118
119
120
121
122
123
124 @Override
125 public String toSerializable(final LogEvent event) {
126 final StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
127
128 buf.append(this.indent1);
129 buf.append('<');
130 if (!complete) {
131 buf.append(this.namespacePrefix);
132 }
133 buf.append("Event logger=\"");
134 String name = event.getLoggerName();
135 if (name.isEmpty()) {
136 name = "root";
137 }
138 buf.append(Transform.escapeHtmlTags(name));
139 buf.append("\" timestamp=\"");
140 buf.append(event.getMillis());
141 buf.append("\" level=\"");
142 buf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
143 buf.append("\" thread=\"");
144 buf.append(Transform.escapeHtmlTags(event.getThreadName()));
145 buf.append("\">");
146 buf.append(this.eol);
147
148 final Message msg = event.getMessage();
149 if (msg != null) {
150 boolean xmlSupported = false;
151 if (msg instanceof MultiformatMessage) {
152 final String[] formats = ((MultiformatMessage) msg).getFormats();
153 for (final String format : formats) {
154 if (format.equalsIgnoreCase("XML")) {
155 xmlSupported = true;
156 break;
157 }
158 }
159 }
160 buf.append(this.indent2);
161 buf.append('<');
162 if (!complete) {
163 buf.append(this.namespacePrefix);
164 }
165 buf.append("Message>");
166 if (xmlSupported) {
167 buf.append(((MultiformatMessage) msg).getFormattedMessage(FORMATS));
168 } else {
169 buf.append("<![CDATA[");
170
171
172 Transform.appendEscapingCDATA(buf, event.getMessage().getFormattedMessage());
173 buf.append("]]>");
174 }
175 buf.append("</");
176 if (!complete) {
177 buf.append(this.namespacePrefix);
178 }
179 buf.append("Message>");
180 buf.append(this.eol);
181 }
182
183 if (event.getContextStack().getDepth() > 0) {
184 buf.append(this.indent2);
185 buf.append('<');
186 if (!complete) {
187 buf.append(this.namespacePrefix);
188 }
189 buf.append("NDC><![CDATA[");
190 Transform.appendEscapingCDATA(buf, event.getContextStack().toString());
191 buf.append("]]></");
192 if (!complete) {
193 buf.append(this.namespacePrefix);
194 }
195 buf.append("NDC>");
196 buf.append(this.eol);
197 }
198
199 if (event.getMarker() != null) {
200 final Marker marker = event.getMarker();
201 buf.append(this.indent2);
202 buf.append('<');
203 if (!complete) {
204 buf.append(this.namespacePrefix);
205 }
206 buf.append("Marker");
207 final Marker parent = marker.getParent();
208 if (parent != null) {
209 buf.append(" parent=\"").append(Transform.escapeHtmlTags(parent.getName())).append("\"");
210 }
211 buf.append('>');
212 buf.append(Transform.escapeHtmlTags(marker.getName()));
213 buf.append("</");
214 if (!complete) {
215 buf.append(this.namespacePrefix);
216 }
217 buf.append("Marker>");
218 buf.append(this.eol);
219 }
220
221 final Throwable throwable = event.getThrown();
222 if (throwable != null) {
223 final List<String> s = Throwables.toStringList(throwable);
224 buf.append(this.indent2);
225 buf.append('<');
226 if (!complete) {
227 buf.append(this.namespacePrefix);
228 }
229 buf.append("Throwable><![CDATA[");
230 for (final String str : s) {
231 Transform.appendEscapingCDATA(buf, str);
232 buf.append(this.eol);
233 }
234 buf.append("]]></");
235 if (!complete) {
236 buf.append(this.namespacePrefix);
237 }
238 buf.append("Throwable>");
239 buf.append(this.eol);
240 }
241
242 if (locationInfo) {
243 final StackTraceElement element = event.getSource();
244 buf.append(this.indent2);
245 buf.append('<');
246 if (!complete) {
247 buf.append(this.namespacePrefix);
248 }
249 buf.append("LocationInfo class=\"");
250 buf.append(Transform.escapeHtmlTags(element.getClassName()));
251 buf.append("\" method=\"");
252 buf.append(Transform.escapeHtmlTags(element.getMethodName()));
253 buf.append("\" file=\"");
254 buf.append(Transform.escapeHtmlTags(element.getFileName()));
255 buf.append("\" line=\"");
256 buf.append(element.getLineNumber());
257 buf.append("\"/>");
258 buf.append(this.eol);
259 }
260
261 if (properties && event.getContextMap().size() > 0) {
262 buf.append(this.indent2);
263 buf.append('<');
264 if (!complete) {
265 buf.append(this.namespacePrefix);
266 }
267 buf.append("Properties>");
268 buf.append(this.eol);
269 for (final Map.Entry<String, String> entry : event.getContextMap().entrySet()) {
270 buf.append(this.indent3);
271 buf.append('<');
272 if (!complete) {
273 buf.append(this.namespacePrefix);
274 }
275 buf.append("Data name=\"");
276 buf.append(Transform.escapeHtmlTags(entry.getKey()));
277 buf.append("\" value=\"");
278 buf.append(Transform.escapeHtmlTags(String.valueOf(entry.getValue())));
279 buf.append("\"/>");
280 buf.append(this.eol);
281 }
282 buf.append(this.indent2);
283 buf.append("</");
284 if (!complete) {
285 buf.append(this.namespacePrefix);
286 }
287 buf.append("Properties>");
288 buf.append(this.eol);
289 }
290
291 buf.append(this.indent1);
292 buf.append("</");
293 if (!complete) {
294 buf.append(this.namespacePrefix);
295 }
296 buf.append("Event>");
297 buf.append(this.eol);
298
299 return buf.toString();
300 }
301
302
303
304
305
306
307
308
309
310
311 @Override
312 public byte[] getHeader() {
313 if (!complete) {
314 return null;
315 }
316 final StringBuilder buf = new StringBuilder();
317 buf.append("<?xml version=\"1.0\" encoding=\"");
318 buf.append(this.getCharset().name());
319 buf.append("\"?>");
320 buf.append(this.eol);
321
322 buf.append('<');
323 buf.append(ROOT_TAG);
324 buf.append(" xmlns=\"" + XML_NAMESPACE + "\">");
325 buf.append(this.eol);
326 return buf.toString().getBytes(this.getCharset());
327 }
328
329
330
331
332
333
334
335 @Override
336 public byte[] getFooter() {
337 if (!complete) {
338 return null;
339 }
340 return ("</" + ROOT_TAG + ">" + this.eol).getBytes(getCharset());
341 }
342
343
344
345
346
347
348
349 @Override
350 public Map<String, String> getContentFormat() {
351 final Map<String, String> result = new HashMap<String, String>();
352
353 result.put("xsd", "log4j-events.xsd");
354 result.put("version", "2.0");
355 return result;
356 }
357
358 @Override
359
360
361
362 public String getContentType() {
363 return "text/xml; charset=" + this.getCharset();
364 }
365
366
367
368
369
370
371
372
373
374
375
376
377 @PluginFactory
378 public static XMLLayout createLayout(
379 @PluginAttribute("locationInfo") final String locationInfo,
380 @PluginAttribute("properties") final String properties,
381 @PluginAttribute("complete") final String completeStr,
382 @PluginAttribute("compact") final String compactStr,
383 @PluginAttribute("namespacePrefix") final String namespacePrefix,
384 @PluginAttribute("charset") final String charsetName) {
385 final Charset charset = Charsets.getSupportedCharset(charsetName, Charsets.UTF_8);
386 final boolean info = Boolean.parseBoolean(locationInfo);
387 final boolean props = Boolean.parseBoolean(properties);
388 final boolean complete = Boolean.parseBoolean(completeStr);
389 final boolean compact = Boolean.parseBoolean(compactStr);
390 return new XMLLayout(info, props, complete, compact, namespacePrefix, charset);
391 }
392 }