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.io.BufferedReader;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.io.Reader;
25 import java.net.MalformedURLException;
26 import java.net.URL;
27 import java.util.Collection;
28 import java.util.Iterator;
29
30 import org.apache.log4j.helpers.Constants;
31 import org.apache.log4j.plugins.Receiver;
32 import org.apache.log4j.rule.ExpressionRule;
33 import org.apache.log4j.rule.Rule;
34 import org.apache.log4j.spi.Decoder;
35 import org.apache.log4j.spi.LoggingEvent;
36
37 /**
38 * LogFileXMLReceiver will read an xml-formated log file and make the events in the log file
39 * available to the log4j framework.
40 * <p>
41 * This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging
42 * XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
43 * <p>
44 * By default, log4j's XMLLayout is supported (no need to specify a decoder in that case).
45 * <p>
46 * To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param
47 * of org.apache.log4j.xml.UtilLoggingXMLDecoder.
48 * <p>
49 * Tailing -may- work, but not in all cases (try using a file:// URL). If a process has a log file
50 * open, the receiver may be able to read and tail the file. If the process closes the file and
51 * reopens the file, the receiver may not be able to continue tailing the file.
52 * <p>
53 * An expressionFilter may be specified. Only events passing the expression will be forwarded to the
54 * log4j framework.
55 * <p>
56 * Once the event has been "posted", it will be handled by the appenders currently configured in the
57 * LoggerRespository.
58 *
59 * @author Scott Deboy <sdeboy@apache.org>
60 * @since 1.3
61 */
62
63 public class LogFileXMLReceiver extends Receiver {
64 private String fileURL;
65 private Rule expressionRule;
66 private String filterExpression;
67 private String decoder = "org.apache.log4j.xml.XMLDecoder";
68 private boolean tailing = false;
69
70 private Decoder decoderInstance;
71 private Reader reader;
72 private static final String FILE_KEY = "file";
73 private String host;
74 private String path;
75 private boolean useCurrentThread;
76
77 /**
78 * Accessor
79 *
80 * @return file URL
81 */
82 public String getFileURL() {
83 return fileURL;
84 }
85
86 /**
87 * Specify the URL of the XML-formatted file to process.
88 *
89 * @param fileURL
90 */
91 public void setFileURL(String fileURL) {
92 this.fileURL = fileURL;
93 }
94
95 /**
96 * Accessor
97 *
98 * @return
99 */
100 public String getDecoder() {
101 return decoder;
102 }
103
104 /**
105 * Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file.
106 *
107 * @param _decoder
108 */
109 public void setDecoder(String _decoder) {
110 decoder = _decoder;
111 }
112
113 /**
114 * Accessor
115 *
116 * @return filter expression
117 */
118 public String getFilterExpression() {
119 return filterExpression;
120 }
121
122 /**
123 * Accessor
124 *
125 * @return tailing flag
126 */
127 public boolean isTailing() {
128 return tailing;
129 }
130
131 /**
132 * Set the 'tailing' flag - may only work on file:// URLs and may stop tailing if the writing
133 * process closes the file and reopens.
134 *
135 * @param tailing
136 */
137 public void setTailing(boolean tailing) {
138 this.tailing = tailing;
139 }
140
141 /**
142 * Set the filter expression that will cause only events which pass the filter to be forwarded
143 * to the log4j framework.
144 *
145 * @param filterExpression
146 */
147 public void setFilterExpression(String filterExpression) {
148 this.filterExpression = filterExpression;
149 }
150
151 private boolean passesExpression(LoggingEvent event) {
152 if (event != null) {
153 if (expressionRule != null) {
154 return (expressionRule.evaluate(event, null));
155 }
156 }
157 return true;
158 }
159
160 public static void main(String[] args) {
161 /*
162 * LogFileXMLReceiver test = new LogFileXMLReceiver();
163 * test.setFileURL("file:///c:/samplelog.xml"); test.setFilterExpression("level >= TRACE");
164 * test.activateOptions();
165 */
166 }
167
168 /**
169 * Close the receiver, release any resources that are accessing the file.
170 */
171 public void shutdown() {
172 try {
173 if (reader != null) {
174 reader.close();
175 reader = null;
176 }
177 } catch (IOException ioe) {
178 ioe.printStackTrace();
179 }
180 }
181
182 /**
183 * Process the file
184 */
185 public void activateOptions() {
186 Runnable runnable = new Runnable() {
187 public void run() {
188 try {
189 URL url = new URL(fileURL);
190 host = url.getHost();
191 if (host != null && host.equals("")) {
192 host = FILE_KEY;
193 }
194 path = url.getPath();
195 } catch (MalformedURLException e1) {
196 // TODO Auto-generated catch block
197 e1.printStackTrace();
198 }
199
200 try {
201 if (filterExpression != null) {
202 expressionRule = ExpressionRule.getRule(filterExpression);
203 }
204 } catch (Exception e) {
205 getLogger().warn("Invalid filter expression: " + filterExpression, e);
206 }
207
208 Class c;
209 try {
210 c = Class.forName(decoder);
211 Object o = c.newInstance();
212 if (o instanceof Decoder) {
213 decoderInstance = (Decoder) o;
214 }
215 } catch (ClassNotFoundException e) {
216 // TODO Auto-generated catch block
217 e.printStackTrace();
218 } catch (InstantiationException e) {
219 // TODO Auto-generated catch block
220 e.printStackTrace();
221 } catch (IllegalAccessException e) {
222 // TODO Auto-generated catch block
223 e.printStackTrace();
224 }
225
226 try {
227 reader = new InputStreamReader(new URL(getFileURL()).openStream());
228 process(reader);
229 } catch (FileNotFoundException fnfe) {
230 getLogger().info("file not available");
231 } catch (IOException ioe) {
232 getLogger().warn("unable to load file", ioe);
233 return;
234 }
235 }
236 };
237 if (useCurrentThread) {
238 runnable.run();
239 } else {
240 Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + getName());
241
242 thread.start();
243
244 }
245 }
246
247 private void process(Reader unbufferedReader) throws IOException {
248 BufferedReader bufferedReader = new BufferedReader(unbufferedReader);
249 char[] content = new char[10000];
250 getLogger().debug("processing starting: " + fileURL);
251 int length = 0;
252 do {
253 System.out.println("in do loop-about to process");
254 while ((length = bufferedReader.read(content)) > -1) {
255 processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length)));
256 }
257 if (tailing) {
258 try {
259 Thread.sleep(5000);
260 } catch (InterruptedException e) {
261 // TODO Auto-generated catch block
262 e.printStackTrace();
263 }
264 }
265 } while (tailing);
266 getLogger().debug("processing complete: " + fileURL);
267
268 shutdown();
269 }
270
271 private void processEvents(Collection c) {
272 if (c == null) {
273 return;
274 }
275
276 for (Iterator iter = c.iterator(); iter.hasNext();) {
277 LoggingEvent evt = (LoggingEvent) iter.next();
278 if (passesExpression(evt)) {
279 if (evt.getProperty(Constants.HOSTNAME_KEY) != null) {
280 evt.setProperty(Constants.HOSTNAME_KEY, host);
281 }
282 if (evt.getProperty(Constants.APPLICATION_KEY) != null) {
283 evt.setProperty(Constants.APPLICATION_KEY, path);
284 }
285 doPost(evt);
286 }
287 }
288 }
289
290 /**
291 * When true, this property uses the current Thread to perform the import, otherwise when false
292 * (the default), a new Thread is created and started to manage the import.
293 *
294 * @return
295 */
296 public final boolean isUseCurrentThread() {
297 return useCurrentThread;
298 }
299
300 /**
301 * Sets whether the current Thread or a new Thread is created to perform the import, the default
302 * being false (new Thread created).
303 *
304 * @param useCurrentThread
305 */
306 public final void setUseCurrentThread(boolean useCurrentThread) {
307 this.useCurrentThread = useCurrentThread;
308 }
309
310 }