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