/* | |
* 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.io.BufferedReader; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.io.Reader; | |
import java.net.MalformedURLException; | |
import java.net.URL; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import org.apache.log4j.helpers.Constants; | |
import org.apache.log4j.plugins.Receiver; | |
import org.apache.log4j.rule.ExpressionRule; | |
import org.apache.log4j.rule.Rule; | |
import org.apache.log4j.spi.Decoder; | |
import org.apache.log4j.spi.LoggingEvent; | |
/** | |
* LogFileXMLReceiver will read an xml-formated log file and make the events in the log file | |
* available to the log4j framework. | |
* <p> | |
* This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging | |
* XMLFormatter (via the org.apache.log4j.spi.Decoder interface). | |
* <p> | |
* By default, log4j's XMLLayout is supported (no need to specify a decoder in that case). | |
* <p> | |
* To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param | |
* of org.apache.log4j.xml.UtilLoggingXMLDecoder. | |
* <p> | |
* Tailing -may- work, but not in all cases (try using a file:// URL). If a process has a log file | |
* open, the receiver may be able to read and tail the file. If the process closes the file and | |
* reopens the file, the receiver may not be able to continue tailing the file. | |
* <p> | |
* An expressionFilter may be specified. Only events passing the expression will be forwarded to the | |
* log4j framework. | |
* <p> | |
* Once the event has been "posted", it will be handled by the appenders currently configured in the | |
* LoggerRespository. | |
* | |
* @author Scott Deboy <sdeboy@apache.org> | |
* @since 1.3 | |
*/ | |
public class LogFileXMLReceiver extends Receiver { | |
private String fileURL; | |
private Rule expressionRule; | |
private String filterExpression; | |
private String decoder = "org.apache.log4j.xml.XMLDecoder"; | |
private boolean tailing = false; | |
private Decoder decoderInstance; | |
private Reader reader; | |
private static final String FILE_KEY = "file"; | |
private String host; | |
private String path; | |
private boolean useCurrentThread; | |
/** | |
* Accessor | |
* | |
* @return file URL | |
*/ | |
public String getFileURL() { | |
return fileURL; | |
} | |
/** | |
* Specify the URL of the XML-formatted file to process. | |
* | |
* @param fileURL | |
*/ | |
public void setFileURL(String fileURL) { | |
this.fileURL = fileURL; | |
} | |
/** | |
* Accessor | |
* | |
* @return | |
*/ | |
public String getDecoder() { | |
return decoder; | |
} | |
/** | |
* Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file. | |
* | |
* @param _decoder | |
*/ | |
public void setDecoder(String _decoder) { | |
decoder = _decoder; | |
} | |
/** | |
* Accessor | |
* | |
* @return filter expression | |
*/ | |
public String getFilterExpression() { | |
return filterExpression; | |
} | |
/** | |
* Accessor | |
* | |
* @return tailing flag | |
*/ | |
public boolean isTailing() { | |
return tailing; | |
} | |
/** | |
* Set the 'tailing' flag - may only work on file:// URLs and may stop tailing if the writing | |
* process closes the file and reopens. | |
* | |
* @param tailing | |
*/ | |
public void setTailing(boolean tailing) { | |
this.tailing = tailing; | |
} | |
/** | |
* Set the filter expression that will cause only events which pass the filter to be forwarded | |
* to the log4j framework. | |
* | |
* @param filterExpression | |
*/ | |
public void setFilterExpression(String filterExpression) { | |
this.filterExpression = filterExpression; | |
} | |
private boolean passesExpression(LoggingEvent event) { | |
if (event != null) { | |
if (expressionRule != null) { | |
return (expressionRule.evaluate(event, null)); | |
} | |
} | |
return true; | |
} | |
public static void main(String[] args) { | |
/* | |
* LogFileXMLReceiver test = new LogFileXMLReceiver(); | |
* test.setFileURL("file:///c:/samplelog.xml"); test.setFilterExpression("level >= TRACE"); | |
* test.activateOptions(); | |
*/ | |
} | |
/** | |
* Close the receiver, release any resources that are accessing the file. | |
*/ | |
public void shutdown() { | |
try { | |
if (reader != null) { | |
reader.close(); | |
reader = null; | |
} | |
} catch (IOException ioe) { | |
ioe.printStackTrace(); | |
} | |
} | |
/** | |
* Process the file | |
*/ | |
public void activateOptions() { | |
Runnable runnable = new Runnable() { | |
public void run() { | |
try { | |
URL url = new URL(fileURL); | |
host = url.getHost(); | |
if (host != null && host.equals("")) { | |
host = FILE_KEY; | |
} | |
path = url.getPath(); | |
} catch (MalformedURLException e1) { | |
// TODO Auto-generated catch block | |
e1.printStackTrace(); | |
} | |
try { | |
if (filterExpression != null) { | |
expressionRule = ExpressionRule.getRule(filterExpression); | |
} | |
} catch (Exception e) { | |
getLogger().warn("Invalid filter expression: " + filterExpression, e); | |
} | |
Class c; | |
try { | |
c = Class.forName(decoder); | |
Object o = c.newInstance(); | |
if (o instanceof Decoder) { | |
decoderInstance = (Decoder) o; | |
} | |
} catch (ClassNotFoundException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} catch (InstantiationException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} catch (IllegalAccessException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
try { | |
reader = new InputStreamReader(new URL(getFileURL()).openStream()); | |
process(reader); | |
} catch (FileNotFoundException fnfe) { | |
getLogger().info("file not available"); | |
} catch (IOException ioe) { | |
getLogger().warn("unable to load file", ioe); | |
return; | |
} | |
} | |
}; | |
if (useCurrentThread) { | |
runnable.run(); | |
} else { | |
Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + getName()); | |
thread.start(); | |
} | |
} | |
private void process(Reader unbufferedReader) throws IOException { | |
BufferedReader bufferedReader = new BufferedReader(unbufferedReader); | |
char[] content = new char[10000]; | |
getLogger().debug("processing starting: " + fileURL); | |
int length = 0; | |
do { | |
System.out.println("in do loop-about to process"); | |
while ((length = bufferedReader.read(content)) > -1) { | |
processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length))); | |
} | |
if (tailing) { | |
try { | |
Thread.sleep(5000); | |
} catch (InterruptedException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
} while (tailing); | |
getLogger().debug("processing complete: " + fileURL); | |
shutdown(); | |
} | |
private void processEvents(Collection c) { | |
if (c == null) { | |
return; | |
} | |
for (Iterator iter = c.iterator(); iter.hasNext();) { | |
LoggingEvent evt = (LoggingEvent) iter.next(); | |
if (passesExpression(evt)) { | |
if (evt.getProperty(Constants.HOSTNAME_KEY) != null) { | |
evt.setProperty(Constants.HOSTNAME_KEY, host); | |
} | |
if (evt.getProperty(Constants.APPLICATION_KEY) != null) { | |
evt.setProperty(Constants.APPLICATION_KEY, path); | |
} | |
doPost(evt); | |
} | |
} | |
} | |
/** | |
* When true, this property uses the current Thread to perform the import, otherwise when false | |
* (the default), a new Thread is created and started to manage the import. | |
* | |
* @return | |
*/ | |
public final boolean isUseCurrentThread() { | |
return useCurrentThread; | |
} | |
/** | |
* Sets whether the current Thread or a new Thread is created to perform the import, the default | |
* being false (new Thread created). | |
* | |
* @param useCurrentThread | |
*/ | |
public final void setUseCurrentThread(boolean useCurrentThread) { | |
this.useCurrentThread = useCurrentThread; | |
} | |
} |