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