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 }