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