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