1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.xml;
19
20 import java.awt.Component;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.io.LineNumberReader;
24 import java.io.StringReader;
25 import java.net.URL;
26 import java.util.HashMap;
27 import java.util.Hashtable;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Vector;
31
32 import javax.swing.ProgressMonitorInputStream;
33 import javax.xml.parsers.DocumentBuilder;
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36
37 import org.apache.log4j.Level;
38 import org.apache.log4j.Logger;
39 import org.apache.log4j.spi.Decoder;
40 import org.apache.log4j.spi.LoggingEvent;
41 import org.apache.log4j.spi.ThrowableInformation;
42 import org.apache.log4j.spi.LocationInfo;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Node;
45 import org.w3c.dom.NodeList;
46 import org.xml.sax.InputSource;
47
48
49 /***
50 * Decodes Logging Events in XML formated into elements that are used by
51 * Chainsaw.
52 *
53 * This decoder can process a collection of log4j:event nodes ONLY
54 * (no XML declaration nor eventSet node)
55 *
56 * NOTE: Only a single LoggingEvent is returned from the decode method
57 * even though the DTD supports multiple events nested in an eventSet.
58 *
59 *
60 * @author Scott Deboy (sdeboy@apache.org)
61 * @author Paul Smith (psmith@apache.org)
62 *
63 */
64 public class XMLDecoder implements Decoder {
65 /***
66 * Document prolog.
67 */
68 private static final String BEGINPART =
69 "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
70 + "<!DOCTYPE log4j:eventSet SYSTEM \"http://localhost/log4j.dtd\">"
71 + "<log4j:eventSet version=\"1.2\" "
72 + "xmlns:log4j=\"http://jakarta.apache.org/log4j/\">";
73 /***
74 * Document close.
75 */
76 private static final String ENDPART = "</log4j:eventSet>";
77 /***
78 * Record end.
79 */
80 private static final String RECORD_END = "</log4j:event>";
81
82 /***
83 * Document builder.
84 */
85 private DocumentBuilder docBuilder;
86 /***
87 * Additional properties.
88 */
89 private Map additionalProperties = new HashMap();
90 /***
91 * Partial event.
92 */
93 private String partialEvent;
94 /***
95 * Owner.
96 */
97 private Component owner = null;
98
99 /***
100 * Create new instance.
101 * @param o owner
102 */
103 public XMLDecoder(final Component o) {
104 this();
105 this.owner = o;
106 }
107
108 /***
109 * Create new instance.
110 */
111 public XMLDecoder() {
112 super();
113 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
114 dbf.setValidating(false);
115
116 try {
117 docBuilder = dbf.newDocumentBuilder();
118 docBuilder.setErrorHandler(new SAXErrorHandler());
119 docBuilder.setEntityResolver(new Log4jEntityResolver());
120 } catch (ParserConfigurationException pce) {
121 System.err.println("Unable to get document builder");
122 }
123 }
124
125 /***
126 * Sets an additionalProperty map, where each Key/Value pair is
127 * automatically added to each LoggingEvent as it is decoded.
128 *
129 * This is useful, say, to include the source file name of the Logging events
130 * @param properties additional properties
131 */
132 public void setAdditionalProperties(final Map properties) {
133 this.additionalProperties = properties;
134 }
135
136 /***
137 * Converts the LoggingEvent data in XML string format into an actual
138 * XML Document class instance.
139 * @param data XML fragment
140 * @return dom document
141 */
142 private Document parse(final String data) {
143 if (docBuilder == null || data == null) {
144 return null;
145 }
146 Document document = null;
147
148 try {
149
150
151
152
153
154
155 /***
156 * resetting the length of the StringBuffer is dangerous, particularly
157 * on some JDK 1.4 impls, there's a known Bug that causes a memory leak
158 */
159 StringBuffer buf = new StringBuffer(1024);
160
161 buf.append(BEGINPART);
162 buf.append(data);
163 buf.append(ENDPART);
164
165 InputSource inputSource =
166 new InputSource(new StringReader(buf.toString()));
167 document = docBuilder.parse(inputSource);
168 } catch (Exception e) {
169 e.printStackTrace();
170 }
171
172 return document;
173 }
174
175 /***
176 * Decodes a File into a Vector of LoggingEvents.
177 * @param url the url of a file containing events to decode
178 * @return Vector of LoggingEvents
179 * @throws IOException if IO error during processing.
180 */
181 public Vector decode(final URL url) throws IOException {
182 LineNumberReader reader;
183 if (owner != null) {
184 reader = new LineNumberReader(new InputStreamReader(
185 new ProgressMonitorInputStream(owner,
186 "Loading " + url , url.openStream())));
187 } else {
188 reader = new LineNumberReader(new InputStreamReader(url.openStream()));
189 }
190
191 Vector v = new Vector();
192
193 String line;
194 Vector events;
195 try {
196 while ((line = reader.readLine()) != null) {
197 StringBuffer buffer = new StringBuffer(line);
198 for (int i = 0; i < 1000; i++) {
199 buffer.append(reader.readLine()).append("\n");
200 }
201 events = decodeEvents(buffer.toString());
202 if (events != null) {
203 v.addAll(events);
204 }
205 }
206 } finally {
207 partialEvent = null;
208 try {
209 if (reader != null) {
210 reader.close();
211 }
212 } catch (Exception e) {
213 e.printStackTrace();
214 }
215 }
216 return v;
217 }
218
219 /***
220 * Decodes a String representing a number of events into a
221 * Vector of LoggingEvents.
222 * @param document to decode events from
223 * @return Vector of LoggingEvents
224 */
225 public Vector decodeEvents(final String document) {
226 if (document != null) {
227 if (document.trim().equals("")) {
228 return null;
229 }
230 String newDoc = null;
231 String newPartialEvent = null;
232
233
234
235
236
237
238
239 if (document.lastIndexOf(RECORD_END) == -1) {
240 partialEvent = partialEvent + document;
241 return null;
242 }
243
244 if (document.lastIndexOf(RECORD_END)
245 + RECORD_END.length() < document.length()) {
246 newDoc = document.substring(0,
247 document.lastIndexOf(RECORD_END) + RECORD_END.length());
248 newPartialEvent = document.substring(
249 document.lastIndexOf(RECORD_END) + RECORD_END.length());
250 } else {
251 newDoc = document;
252 }
253 if (partialEvent != null) {
254 newDoc = partialEvent + newDoc;
255 }
256 partialEvent = newPartialEvent;
257 Document doc = parse(newDoc);
258 if (doc == null) {
259 return null;
260 }
261 return decodeEvents(doc);
262 }
263 return null;
264 }
265
266 /***
267 * Converts the string data into an XML Document, and then soaks out the
268 * relevant bits to form a new LoggingEvent instance which can be used
269 * by any Log4j element locally.
270 * @param data XML fragment
271 * @return a single LoggingEvent
272 */
273 public LoggingEvent decode(final String data) {
274 Document document = parse(data);
275
276 if (document == null) {
277 return null;
278 }
279
280 Vector events = decodeEvents(document);
281
282 if (events.size() > 0) {
283 return (LoggingEvent) events.firstElement();
284 }
285
286 return null;
287 }
288
289 /***
290 * Given a Document, converts the XML into a Vector of LoggingEvents.
291 * @param document XML document
292 * @return Vector of LoggingEvents
293 */
294 private Vector decodeEvents(final Document document) {
295 Vector events = new Vector();
296
297 Logger logger;
298 long timeStamp;
299 String level;
300 String threadName;
301 Object message = null;
302 String ndc = null;
303 String[] exception = null;
304 String className = null;
305 String methodName = null;
306 String fileName = null;
307 String lineNumber = null;
308 Hashtable properties = null;
309
310 NodeList nl = document.getElementsByTagName("log4j:eventSet");
311 Node eventSet = nl.item(0);
312
313 NodeList eventList = eventSet.getChildNodes();
314
315 for (int eventIndex = 0; eventIndex < eventList.getLength();
316 eventIndex++) {
317 Node eventNode = eventList.item(eventIndex);
318
319 if (eventNode.getNodeType() != Node.ELEMENT_NODE) {
320 continue;
321 }
322 logger =
323 Logger.getLogger(
324 eventNode.getAttributes().getNamedItem("logger").getNodeValue());
325 timeStamp =
326 Long.parseLong(
327 eventNode.getAttributes().getNamedItem("timestamp").getNodeValue());
328 level = eventNode.getAttributes().getNamedItem("level").getNodeValue();
329 threadName =
330 eventNode.getAttributes().getNamedItem("thread").getNodeValue();
331
332 NodeList list = eventNode.getChildNodes();
333 int listLength = list.getLength();
334
335 for (int y = 0; y < listLength; y++) {
336 String tagName = list.item(y).getNodeName();
337
338 if (tagName.equalsIgnoreCase("log4j:message")) {
339 message = getCData(list.item(y));
340 }
341
342 if (tagName.equalsIgnoreCase("log4j:NDC")) {
343 ndc = getCData(list.item(y));
344 }
345
346 if (tagName.equalsIgnoreCase("log4j:MDC")) {
347 properties = new Hashtable();
348 NodeList propertyList = list.item(y).getChildNodes();
349 int propertyLength = propertyList.getLength();
350
351 for (int i = 0; i < propertyLength; i++) {
352 String propertyTag = propertyList.item(i).getNodeName();
353
354 if (propertyTag.equalsIgnoreCase("log4j:data")) {
355 Node property = propertyList.item(i);
356 String name =
357 property.getAttributes().getNamedItem("name").getNodeValue();
358 String value =
359 property.getAttributes().getNamedItem("value").getNodeValue();
360 properties.put(name, value);
361 }
362 }
363 }
364
365 if (tagName.equalsIgnoreCase("log4j:throwable")) {
366 exception = new String[] {
367 getCData(list.item(y))
368 };
369 }
370
371 if (tagName.equalsIgnoreCase("log4j:locationinfo")) {
372 className =
373 list.item(y).getAttributes().getNamedItem("class").getNodeValue();
374 methodName =
375 list.item(y).getAttributes().getNamedItem("method").getNodeValue();
376 fileName =
377 list.item(y).getAttributes().getNamedItem("file").getNodeValue();
378 lineNumber =
379 list.item(y).getAttributes().getNamedItem("line").getNodeValue();
380 }
381
382 if (tagName.equalsIgnoreCase("log4j:properties")) {
383 if (properties == null) {
384 properties = new Hashtable();
385 }
386 NodeList propertyList = list.item(y).getChildNodes();
387 int propertyLength = propertyList.getLength();
388
389 for (int i = 0; i < propertyLength; i++) {
390 String propertyTag = propertyList.item(i).getNodeName();
391
392 if (propertyTag.equalsIgnoreCase("log4j:data")) {
393 Node property = propertyList.item(i);
394 String name =
395 property.getAttributes().getNamedItem("name").getNodeValue();
396 String value =
397 property.getAttributes().getNamedItem("value").getNodeValue();
398 properties.put(name, value);
399 }
400 }
401 }
402
403 /***
404 * We add all the additional properties to the properties
405 * hashtable. Don't override properties that already exist
406 */
407 if (additionalProperties.size() > 0) {
408 if (properties == null) {
409 properties = new Hashtable(additionalProperties);
410 } else {
411 Iterator i = additionalProperties.entrySet().iterator();
412 while (i.hasNext()) {
413 Map.Entry e = (Map.Entry) i.next();
414 if (!(properties.containsKey(e.getKey()))) {
415 properties.put(e.getKey(), e.getValue());
416 }
417 }
418 }
419 }
420 }
421 Level levelImpl = Level.toLevel(level);
422
423 LocationInfo info;
424 if ((fileName != null)
425 || (className != null)
426 || (methodName != null)
427 || (lineNumber != null)) {
428 info = new LocationInfo(fileName, className, methodName, lineNumber);
429 } else {
430 info = LocationInfo.NA_LOCATION_INFO;
431 }
432 if (exception == null) {
433 exception = new String[]{""};
434 }
435
436 LoggingEvent loggingEvent = new LoggingEvent(null,
437 logger, timeStamp, levelImpl, message,
438 threadName,
439 new ThrowableInformation(exception),
440 ndc,
441 info,
442 properties);
443
444
445 events.add(loggingEvent);
446
447 message = null;
448 ndc = null;
449 exception = null;
450 className = null;
451 methodName = null;
452 fileName = null;
453 lineNumber = null;
454 properties = null;
455 }
456
457 return events;
458 }
459
460 /***
461 * Get contents of CDATASection.
462 * @param n CDATASection
463 * @return text content of all text or CDATA children of node.
464 */
465 private String getCData(final Node n) {
466 StringBuffer buf = new StringBuffer();
467 NodeList nl = n.getChildNodes();
468
469 for (int x = 0; x < nl.getLength(); x++) {
470 Node innerNode = nl.item(x);
471
472 if (
473 (innerNode.getNodeType() == Node.TEXT_NODE)
474 || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
475 buf.append(innerNode.getNodeValue());
476 }
477 }
478
479 return buf.toString();
480 }
481 }