blob: 00d6f7ca270b62658ecdd33750e1017774f2c63d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.log4j.xml;
import java.awt.Component;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import java.util.zip.ZipInputStream;
import javax.swing.ProgressMonitorInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.UtilLoggingLevel;
import org.apache.log4j.spi.Decoder;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import org.apache.log4j.spi.LocationInfo;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Decodes JDK 1.4's java.util.logging package events
* delivered via XML (using the logger.dtd).
*
* @author Scott Deboy (sdeboy@apache.org)
* @author Paul Smith (psmith@apache.org)
*
*/
public class UtilLoggingXMLDecoder implements Decoder {
//NOTE: xml section is only handed on first delivery of events
//on this first delivery of events, there is no end tag for the log element
/**
* Document prolog.
*/
private static final String BEGIN_PART =
"<log>";
/**
* Document close.
*/
private static final String END_PART = "</log>";
/**
* Document builder.
*/
private DocumentBuilder docBuilder;
/**
* Additional properties.
*/
private Map additionalProperties = new HashMap();
/**
* Partial event.
*/
private String partialEvent;
/**
* Record end.
*/
private static final String RECORD_END = "</record>";
/**
* Owner.
*/
private Component owner = null;
private static final String ENCODING = "UTF-8";
/**
* Create new instance.
* @param o owner
*/
public UtilLoggingXMLDecoder(final Component o) {
this();
this.owner = o;
}
/**
* Create new instance.
*/
public UtilLoggingXMLDecoder() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
try {
docBuilder = dbf.newDocumentBuilder();
docBuilder.setErrorHandler(new SAXErrorHandler());
docBuilder.setEntityResolver(new UtilLoggingEntityResolver());
} catch (ParserConfigurationException pce) {
System.err.println("Unable to get document builder");
}
}
/**
* Sets an additionalProperty map, where each Key/Value pair is
* automatically added to each LoggingEvent as it is decoded.
*
* This is useful, say, to include the source file name of the Logging events
* @param properties additional properties
*/
public void setAdditionalProperties(final Map properties) {
this.additionalProperties = properties;
}
/**
* Converts the LoggingEvent data in XML string format into an actual
* XML Document class instance.
* @param data XML fragment
* @return dom document
*/
private Document parse(final String data) {
if (docBuilder == null || data == null) {
return null;
}
Document document = null;
try {
// we change the system ID to a valid URI so that Crimson won't
// complain. Indeed, "log4j.dtd" alone is not a valid URI which
// causes Crimson to barf. The Log4jEntityResolver only cares
// about the "log4j.dtd" ending.
/**
* resetting the length of the StringBuffer is dangerous, particularly
* on some JDK 1.4 impls, there's a known Bug that causes a memory leak
*/
StringBuilder buf = new StringBuilder(1024);
if (!data.startsWith("<?xml")) {
buf.append(BEGIN_PART);
}
buf.append(data);
if (!data.endsWith(END_PART)) {
buf.append(END_PART);
}
InputSource inputSource =
new InputSource(new StringReader(buf.toString()));
document = docBuilder.parse(inputSource);
} catch (Exception e) {
e.printStackTrace();
}
return document;
}
/**
* Decodes a File into a Vector of LoggingEvents.
* @param url the url of a file containing events to decode
* @return Vector of LoggingEvents
* @throws IOException if IO error during processing.
*/
public Vector<LoggingEvent> decode(final URL url) throws IOException {
LineNumberReader reader;
boolean isZipFile = url.getPath().toLowerCase().endsWith(".zip");
InputStream inputStream;
if (isZipFile) {
inputStream = new ZipInputStream(url.openStream());
//move stream to next entry so we can read it
((ZipInputStream)inputStream).getNextEntry();
} else {
inputStream = url.openStream();
}
if (owner != null) {
reader = new LineNumberReader(
new InputStreamReader(
new ProgressMonitorInputStream(owner,
"Loading " + url , inputStream), ENCODING));
} else {
reader = new LineNumberReader(new InputStreamReader(inputStream, ENCODING));
}
Vector<LoggingEvent> v = new Vector<>();
String line;
Vector<LoggingEvent> events;
try {
while ((line = reader.readLine()) != null) {
StringBuilder buffer = new StringBuilder(line);
for (int i = 0; i < 1000; i++) {
buffer.append(reader.readLine()).append("\n");
}
events = decodeEvents(buffer.toString());
if (events != null) {
v.addAll(events);
}
}
} finally {
partialEvent = null;
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return v;
}
/**
* Decodes a String representing a number of events into a
* Vector of LoggingEvents.
* @param document to decode events from
* @return Vector of LoggingEvents
*/
public Vector<LoggingEvent> decodeEvents(final String document) {
if (document != null) {
if (document.trim().equals("")) {
return null;
}
String newDoc;
String newPartialEvent = null;
//separate the string into the last portion ending with </record>
// (which will be processed) and the partial event which
// will be combined and processed in the next section
//if the document does not contain a record end,
// append it to the partial event string
if (document.lastIndexOf(RECORD_END) == -1) {
partialEvent = partialEvent + document;
return null;
}
if (document.lastIndexOf(RECORD_END) + RECORD_END.length()
< document.length()) {
newDoc = document.substring(0,
document.lastIndexOf(RECORD_END) + RECORD_END.length());
newPartialEvent = document.substring(
document.lastIndexOf(RECORD_END) + RECORD_END.length());
} else {
newDoc = document;
}
if (partialEvent != null) {
newDoc = partialEvent + newDoc;
}
partialEvent = newPartialEvent;
Document doc = parse(newDoc);
if (doc == null) {
return null;
}
return decodeEvents(doc);
}
return null;
}
/**
* Converts the string data into an XML Document, and then soaks out the
* relevant bits to form a new LoggingEvent instance which can be used
* by any Log4j element locally.
* @param data XML fragment
* @return a single LoggingEvent or null
*/
public LoggingEvent decode(final String data) {
Document document = parse(data);
if (document == null) {
return null;
}
Vector<LoggingEvent> events = decodeEvents(document);
if (events.size() > 0) {
return events.firstElement();
}
return null;
}
/**
* Given a Document, converts the XML into a Vector of LoggingEvents.
* @param document XML document
* @return Vector of LoggingEvents
*/
private Vector<LoggingEvent> decodeEvents(final Document document) {
Vector<LoggingEvent> events = new Vector<>();
NodeList eventList = document.getElementsByTagName("record");
for (int eventIndex = 0; eventIndex < eventList.getLength();
eventIndex++) {
Node eventNode = eventList.item(eventIndex);
Logger logger = null;
long timeStamp = 0L;
Level level = null;
String threadName = null;
Object message = null;
String ndc = null;
String[] exception = null;
String className = null;
String methodName = null;
String fileName = null;
String lineNumber = null;
Hashtable properties = new Hashtable();
//format of date: 2003-05-04T11:04:52
//ignore date or set as a property? using millis in constructor instead
NodeList list = eventNode.getChildNodes();
int listLength = list.getLength();
if (listLength == 0) {
continue;
}
for (int y = 0; y < listLength; y++) {
String tagName = list.item(y).getNodeName();
if (tagName.equalsIgnoreCase("logger")) {
logger = Logger.getLogger(getCData(list.item(y)));
}
if (tagName.equalsIgnoreCase("millis")) {
timeStamp = Long.parseLong(getCData(list.item(y)));
}
if (tagName.equalsIgnoreCase("level")) {
level = UtilLoggingLevel.toLevel(getCData(list.item(y)));
}
if (tagName.equalsIgnoreCase("thread")) {
threadName = getCData(list.item(y));
}
if (tagName.equalsIgnoreCase("sequence")) {
properties.put("log4jid", getCData(list.item(y)));
}
if (tagName.equalsIgnoreCase("message")) {
message = getCData(list.item(y));
}
if (tagName.equalsIgnoreCase("class")) {
className = getCData(list.item(y));
}
if (tagName.equalsIgnoreCase("method")) {
methodName = getCData(list.item(y));
}
if (tagName.equalsIgnoreCase("exception")) {
ArrayList<String> exceptionList = new ArrayList<>();
NodeList exList = list.item(y).getChildNodes();
int exlistLength = exList.getLength();
for (int i2 = 0; i2 < exlistLength; i2++) {
Node exNode = exList.item(i2);
String exName = exList.item(i2).getNodeName();
if (exName.equalsIgnoreCase("message")) {
exceptionList.add(getCData(exList.item(i2)));
}
if (exName.equalsIgnoreCase("frame")) {
NodeList exList2 = exNode.getChildNodes();
int exlist2Length = exList2.getLength();
for (int i3 = 0; i3 < exlist2Length; i3++) {
exceptionList.add(getCData(exList2.item(i3)) + "\n");
}
}
}
if (exceptionList.size() > 0) {
exception =
(String[]) exceptionList.toArray(new String[exceptionList.size()]);
}
}
}
/**
* We add all the additional properties to the properties
* hashtable. Override properties that already exist
*/
if (additionalProperties.size() > 0) {
if (properties == null) {
properties = new Hashtable(additionalProperties);
}
for (Object o : additionalProperties.entrySet()) {
Map.Entry e = (Map.Entry) o;
properties.put(e.getKey(), e.getValue());
}
}
LocationInfo info;
if ((fileName != null)
|| (className != null)
|| (methodName != null)
|| (lineNumber != null)) {
info = new LocationInfo(fileName, className, methodName, lineNumber);
} else {
info = LocationInfo.NA_LOCATION_INFO;
}
ThrowableInformation throwableInfo = null;
if (exception != null) {
throwableInfo = new ThrowableInformation(exception);
}
LoggingEvent loggingEvent = new LoggingEvent(null,
logger, timeStamp, level, message,
threadName,
throwableInfo,
ndc,
info,
properties);
events.add(loggingEvent);
}
return events;
}
/**
* Get contents of CDATASection.
* @param n CDATASection
* @return text content of all text or CDATA children of node.
*/
private String getCData(final Node n) {
StringBuilder buf = new StringBuilder();
NodeList nl = n.getChildNodes();
for (int x = 0; x < nl.getLength(); x++) {
Node innerNode = nl.item(x);
if (
(innerNode.getNodeType() == Node.TEXT_NODE)
|| (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
buf.append(innerNode.getNodeValue());
}
}
return buf.toString();
}
}