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 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 &lt;sdeboy@apache.org&gt;
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 }