1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.varia;
18
19 import java.io.BufferedReader;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.io.Reader;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.text.SimpleDateFormat;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Hashtable;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.TreeMap;
36
37 import org.apache.log4j.Level;
38 import org.apache.log4j.Logger;
39 import org.apache.log4j.helpers.Constants;
40 import org.apache.log4j.plugins.Receiver;
41 import org.apache.log4j.rule.ExpressionRule;
42 import org.apache.log4j.rule.Rule;
43 import org.apache.log4j.spi.LoggingEvent;
44 import org.apache.log4j.spi.ThrowableInformation;
45 import org.apache.log4j.spi.LocationInfo;
46 import org.apache.oro.text.perl.Perl5Util;
47 import org.apache.oro.text.regex.MalformedPatternException;
48 import org.apache.oro.text.regex.MatchResult;
49 import org.apache.oro.text.regex.Pattern;
50 import org.apache.oro.text.regex.Perl5Compiler;
51 import org.apache.oro.text.regex.Perl5Matcher;
52
53 /***
54 * LogFilePatternReceiver can parse and tail log files, converting entries into
55 * LoggingEvents. If the file doesn't exist when the receiver is initialized, the
56 * receiver will look for the file once every 10 seconds.
57 * <p>
58 * This receiver relies on ORO Perl5 features to perform the parsing of text in the
59 * log file, however the only regular expression field explicitly supported is
60 * a glob-style wildcard used to ignore fields in the log file if needed. All other
61 * fields are parsed by using the supplied keywords.
62 * <p>
63 * <b>Features:</b><br>
64 * - specify the URL of the log file to be processed<br>
65 * - specify the timestamp format in the file (if one exists)<br>
66 * - specify the pattern (logFormat) used in the log file using keywords, a wildcard character (*) and fixed text<br>
67 * - 'tail' the file (allows the contents of the file to be continually read and new events processed)<br>
68 * - supports the parsing of multi-line messages and exceptions
69 * - 'hostname' property set to URL host (or 'file' if not available)
70 * - 'application' property set to URL path (or value of fileURL if not available)
71 *<p>
72 * <b>Keywords:</b><br>
73 * TIMESTAMP<br>
74 * LOGGER<br>
75 * LEVEL<br>
76 * THREAD<br>
77 * CLASS<br>
78 * FILE<br>
79 * LINE<br>
80 * METHOD<br>
81 * RELATIVETIME<br>
82 * MESSAGE<br>
83 * NDC<br>
84 * PROP(key)<br>
85 * <p>
86 * Use a * to ignore portions of the log format that should be ignored
87 * <p>
88 * Example:<br>
89 * If your file's patternlayout is this:<br>
90 * <b>%d %-5p [%t] %C{2} (%F:%L) - %m%n</b>
91 *<p>
92 * specify this as the log format:<br>
93 * <b>TIMESTAMP LEVEL [THREAD] CLASS (FILE:LINE) - MESSAGE</b>
94 *<p>
95 * To define a PROPERTY field, use PROP(key)
96 * <p>
97 * Example:<br>
98 * If you used the RELATIVETIME pattern layout character in the file,
99 * you can use PROP(RELATIVETIME) in the logFormat definition to assign
100 * the RELATIVETIME field as a property on the event.
101 * <p>
102 * If your file's patternlayout is this:<br>
103 * <b>%r [%t] %-5p %c %x - %m%n</b>
104 *<p>
105 * specify this as the log format:<br>
106 * <b>PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE</b>
107 * <p>
108 * Note the * - it can be used to ignore a single word or sequence of words in the log file
109 * (in order for the wildcard to ignore a sequence of words, the text being ignored must be
110 * followed by some delimiter, like '-' or '[') - ndc is being ignored in this example.
111 * <p>
112 * Assign a filterExpression in order to only process events which match a filter.
113 * If a filterExpression is not assigned, all events are processed.
114 *<p>
115 * <b>Limitations:</b><br>
116 * - no support for the single-line version of throwable supported by patternlayout<br>
117 * (this version of throwable will be included as the last line of the message)<br>
118 * - the relativetime patternLayout character must be set as a property: PROP(RELATIVETIME)<br>
119 * - messages should appear as the last field of the logFormat because the variability in message content<br>
120 * - exceptions are converted if the exception stack trace (other than the first line of the exception)<br>
121 * is stored in the log file with a tab followed by the word 'at' as the first characters in the line<br>
122 * - tailing may fail if the file rolls over.
123 *<p>
124 * <b>Example receiver configuration settings</b> (add these as params, specifying a LogFilePatternReceiver 'plugin'):<br>
125 * param: "timestampFormat" value="yyyy-MM-d HH:mm:ss,SSS"<br>
126 * param: "logFormat" value="RELATIVETIME [THREAD] LEVEL LOGGER * - MESSAGE"<br>
127 * param: "fileURL" value="file:///c:/events.log"<br>
128 * param: "tailing" value="true"
129 *<p>
130 * This configuration will be able to process these sample events:<br>
131 * 710 [ Thread-0] DEBUG first.logger first - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br>
132 * 880 [ Thread-2] DEBUG first.logger third - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br>
133 * 880 [ Thread-0] INFO first.logger first - infomsg-0<br>
134 * java.lang.Exception: someexception-first<br>
135 * at Generator2.run(Generator2.java:102)<br>
136 *
137 *@author Scott Deboy
138 */
139 public class LogFilePatternReceiver extends Receiver {
140 private final Set keywords = new HashSet();
141
142 private static final String PROP_START = "PROP(";
143 private static final String PROP_END = ")";
144
145 private static final String LOGGER = "LOGGER";
146 private static final String MESSAGE = "MESSAGE";
147 private static final String TIMESTAMP = "TIMESTAMP";
148 private static final String NDC = "NDC";
149 private static final String LEVEL = "LEVEL";
150 private static final String THREAD = "THREAD";
151 private static final String CLASS = "CLASS";
152 private static final String FILE = "FILE";
153 private static final String LINE = "LINE";
154 private static final String METHOD = "METHOD";
155
156 private static final String DEFAULT_HOST = "file";
157
158
159 private static final String EXCEPTION_PATTERN = "\tat.*";
160 private static final String REGEXP_DEFAULT_WILDCARD = ".+?";
161 private static final String REGEXP_GREEDY_WILDCARD = ".+";
162 private static final String PATTERN_WILDCARD = "*";
163 private static final String DEFAULT_GROUP = "(" + REGEXP_DEFAULT_WILDCARD + ")";
164 private static final String GREEDY_GROUP = "(" + REGEXP_GREEDY_WILDCARD + ")";
165 private static final String MULTIPLE_SPACES_REGEXP = "[ ]+";
166
167 private final String newLine = System.getProperty("line.separator");
168
169 private final String[] emptyException = new String[] { "" };
170
171 private SimpleDateFormat dateFormat;
172 private String timestampFormat = "yyyy-MM-d HH:mm:ss,SSS";
173 private String logFormat;
174 private String fileURL;
175 private String host;
176 private String path;
177 private boolean tailing;
178 private String filterExpression;
179
180 private Perl5Util util = null;
181 private Perl5Compiler exceptionCompiler = null;
182 private Perl5Matcher exceptionMatcher = null;
183 private static final String VALID_DATEFORMAT_CHAR_PATTERN = "[GyMwWDdFEaHkKhmsSzZ]";
184
185 private Rule expressionRule;
186
187 private Map currentMap;
188 private List additionalLines;
189 private List matchingKeywords;
190
191 private String regexp;
192 private Reader reader;
193 private Pattern regexpPattern;
194 private String timestampPatternText;
195
196 private boolean useCurrentThread;
197
198 public LogFilePatternReceiver() {
199 keywords.add(TIMESTAMP);
200 keywords.add(LOGGER);
201 keywords.add(LEVEL);
202 keywords.add(THREAD);
203 keywords.add(CLASS);
204 keywords.add(FILE);
205 keywords.add(LINE);
206 keywords.add(METHOD);
207 keywords.add(MESSAGE);
208 keywords.add(NDC);
209 }
210
211 /***
212 * Accessor
213 *
214 * @return file URL
215 */
216 public String getFileURL() {
217 return fileURL;
218 }
219
220 /***
221 * Mutator
222 *
223 * @param fileURL
224 */
225 public void setFileURL(String fileURL) {
226 this.fileURL = fileURL;
227 }
228
229 /***
230 * Accessor
231 *
232 * @return filter expression
233 */
234 public String getFilterExpression() {
235 return filterExpression;
236 }
237
238 /***
239 * Mutator
240 *
241 * @param filterExpression
242 */
243 public void setFilterExpression(String filterExpression) {
244 this.filterExpression = filterExpression;
245 }
246
247 /***
248 * Accessor
249 *
250 * @return tailing
251 */
252 public boolean isTailing() {
253 return tailing;
254 }
255
256 /***
257 * Mutator
258 *
259 * @param tailing
260 */
261 public void setTailing(boolean tailing) {
262 this.tailing = tailing;
263 }
264
265 /***
266 * When true, this property uses the current Thread to perform the import,
267 * otherwise when false (the default), a new Thread is created and started to manage
268 * the import.
269 * @return
270 */
271 public final boolean isUseCurrentThread() {
272 return useCurrentThread;
273 }
274
275 /***
276 * Sets whether the current Thread or a new Thread is created to perform the import,
277 * the default being false (new Thread created).
278 *
279 * @param useCurrentThread
280 */
281 public final void setUseCurrentThread(boolean useCurrentThread) {
282 this.useCurrentThread = useCurrentThread;
283 }
284
285 /***
286 * Accessor
287 *
288 * @return log format
289 */
290 public String getLogFormat() {
291 return logFormat;
292 }
293
294 /***
295 * Mutator
296 *
297 * @param logFormat
298 * the format
299 */
300 public void setLogFormat(String logFormat) {
301 this.logFormat = logFormat;
302 }
303
304 /***
305 * Mutator
306 *
307 * @param timestampFormat
308 */
309 public void setTimestampFormat(String timestampFormat) {
310 this.timestampFormat = timestampFormat;
311 }
312
313 /***
314 * Accessor
315 *
316 * @return timestamp format
317 */
318 public String getTimestampFormat() {
319 return timestampFormat;
320 }
321
322 /***
323 * Walk the additionalLines list, looking for the EXCEPTION_PATTERN.
324 * <p>
325 * Return the index of the first matched line minus 1
326 * (the match is the 2nd line of an exception)
327 * <p>
328 * Assumptions: <br>
329 * - the additionalLines list may contain both message and exception lines<br>
330 * - message lines are added to the additionalLines list and then
331 * exception lines (all message lines occur in the list prior to all
332 * exception lines)
333 *
334 * @return -1 if no exception line exists, line number otherwise
335 */
336 private int getExceptionLine() {
337 try {
338 Pattern exceptionPattern = exceptionCompiler.compile(EXCEPTION_PATTERN);
339 for (int i = 0; i < additionalLines.size(); i++) {
340 if (exceptionMatcher.matches((String) additionalLines.get(i), exceptionPattern)) {
341 return i - 1;
342 }
343 }
344 } catch (MalformedPatternException mpe) {
345 getLogger().warn("Bad pattern: " + EXCEPTION_PATTERN);
346 }
347 return -1;
348 }
349
350 /***
351 * Combine all message lines occuring in the additionalLines list, adding
352 * a newline character between each line
353 * <p>
354 * the event will already have a message - combine this message
355 * with the message lines in the additionalLines list
356 * (all entries prior to the exceptionLine index)
357 *
358 * @param firstMessageLine primary message line
359 * @param exceptionLine index of first exception line
360 * @return message
361 */
362 private String buildMessage(String firstMessageLine, int exceptionLine) {
363 if (additionalLines.size() == 0 || exceptionLine == 0) {
364 return firstMessageLine;
365 }
366 StringBuffer message = new StringBuffer();
367 if (firstMessageLine != null) {
368 message.append(firstMessageLine);
369 }
370
371 int linesToProcess = (exceptionLine == -1?additionalLines.size(): exceptionLine);
372
373 for (int i = 0; i < linesToProcess; i++) {
374 message.append(newLine);
375 message.append(additionalLines.get(i));
376 }
377 return message.toString();
378 }
379
380 /***
381 * Combine all exception lines occuring in the additionalLines list into a
382 * String array
383 * <p>
384 * (all entries equal to or greater than the exceptionLine index)
385 *
386 * @param exceptionLine index of first exception line
387 * @return exception
388 */
389 private String[] buildException(int exceptionLine) {
390 if (exceptionLine == -1) {
391 return emptyException;
392 }
393 String[] exception = new String[additionalLines.size() - exceptionLine];
394 for (int i = 0; i < additionalLines.size() - exceptionLine; i++) {
395 exception[i] = (String) additionalLines.get(i + exceptionLine);
396 }
397 return exception;
398 }
399
400 /***
401 * Construct a logging event from currentMap and additionalLines
402 * (additionalLines contains multiple message lines and any exception lines)
403 * <p>
404 * CurrentMap and additionalLines are cleared in the process
405 *
406 * @return event
407 */
408 private LoggingEvent buildEvent() {
409 if (currentMap.size() == 0) {
410 if (additionalLines.size() > 0) {
411 for (Iterator iter = additionalLines.iterator();iter.hasNext();) {
412 getLogger().info("found non-matching line: " + iter.next());
413 }
414 }
415 additionalLines.clear();
416 return null;
417 }
418
419 int exceptionLine = getExceptionLine();
420 String[] exception = buildException(exceptionLine);
421
422
423 if (additionalLines.size() > 0 && exceptionLine != 0) {
424 currentMap.put(MESSAGE, buildMessage((String) currentMap.get(MESSAGE),
425 exceptionLine));
426 }
427 LoggingEvent event = convertToEvent(currentMap, exception);
428 currentMap.clear();
429 additionalLines.clear();
430 return event;
431 }
432
433 /***
434 * Read, parse and optionally tail the log file, converting entries into logging events.
435 *
436 * A runtimeException is thrown if the logFormat pattern is malformed
437 * according to ORO's Perl5Compiler.
438 *
439 * @param unbufferedReader
440 * @throws IOException
441 */
442 protected void process(BufferedReader bufferedReader) throws IOException {
443
444 Perl5Matcher eventMatcher = new Perl5Matcher();
445 String line;
446 while ((line = bufferedReader.readLine()) != null) {
447 if (eventMatcher.matches(line, regexpPattern)) {
448
449 LoggingEvent event = buildEvent();
450 if (event != null) {
451 if (passesExpression(event)) {
452 doPost(event);
453 }
454 }
455 currentMap.putAll(processEvent(eventMatcher.getMatch()));
456 } else {
457
458
459 additionalLines.add(line);
460 }
461 }
462
463
464 LoggingEvent event = buildEvent();
465 if (event != null) {
466 if (passesExpression(event)) {
467 doPost(event);
468 }
469 getLogger().debug("no further lines to process in " + fileURL);
470 }
471 }
472
473 /***
474 * create the regular expression pattern using the input regular expression
475 */
476 protected void createPattern() {
477 Perl5Compiler compiler = new Perl5Compiler();
478 try {
479 regexpPattern = compiler.compile(regexp);
480 } catch (MalformedPatternException mpe) {
481 throw new RuntimeException("Bad pattern: " + regexp);
482 }
483 }
484
485 /***
486 * Helper method that supports the evaluation of the expression
487 *
488 * @param event
489 * @return true if expression isn't set, or the result of the evaluation otherwise
490 */
491 private boolean passesExpression(LoggingEvent event) {
492 if (event != null) {
493 if (expressionRule != null) {
494 return (expressionRule.evaluate(event));
495 }
496 }
497 return true;
498 }
499
500 /***
501 * Convert the ORO match into a map.
502 * <p>
503 * Relies on the fact that the matchingKeywords list is in the same
504 * order as the groups in the regular expression
505 *
506 * @param result
507 * @return map
508 */
509 private Map processEvent(MatchResult result) {
510 Map map = new HashMap();
511
512 for (int i = 1; i < result.groups(); i++) {
513 map.put(matchingKeywords.get(i - 1), result.group(i));
514 }
515 return map;
516 }
517
518 /***
519 * Helper method that will convert timestamp format to a pattern
520 *
521 *
522 * @return string
523 */
524 private String convertTimestamp() {
525 return util.substitute("s/("+VALID_DATEFORMAT_CHAR_PATTERN+")+/////w+/g", timestampFormat);
526 }
527
528 protected void setHost(String host) {
529 this.host = host;
530 }
531
532 protected void setPath(String path) {
533 this.path = path;
534 }
535
536 /***
537 * Build the regular expression needed to parse log entries
538 *
539 */
540 protected void initialize() {
541 if (host == null && path == null) {
542 try {
543 URL url = new URL(fileURL);
544 host = url.getHost();
545 path = url.getPath();
546 } catch (MalformedURLException e1) {
547
548 e1.printStackTrace();
549 }
550 }
551 if (host == null || host.trim().equals("")) {
552 host = DEFAULT_HOST;
553 }
554 if (path == null || path.trim().equals("")) {
555 path = fileURL;
556 }
557
558 util = new Perl5Util();
559 exceptionCompiler = new Perl5Compiler();
560 exceptionMatcher = new Perl5Matcher();
561
562 currentMap = new HashMap();
563 additionalLines = new ArrayList();
564 matchingKeywords = new ArrayList();
565
566 if (timestampFormat != null) {
567 dateFormat = new SimpleDateFormat(timestampFormat);
568 timestampPatternText = convertTimestamp();
569 }
570
571 try {
572 if (filterExpression != null) {
573 expressionRule = ExpressionRule.getRule(filterExpression);
574 }
575 } catch (Exception e) {
576 getLogger().warn("Invalid filter expression: " + filterExpression, e);
577 }
578
579 Map keywordMap = new TreeMap();
580
581 String newPattern = logFormat;
582
583
584
585
586
587
588
589
590 int index = 0;
591 int currentPosition = 0;
592 String current = newPattern;
593 while (index > -1) {
594 index = current.indexOf(PROP_START);
595 currentPosition = currentPosition + index;
596 if (index > -1) {
597 String currentProp = current.substring(current.indexOf(PROP_START));
598 String prop = currentProp.substring(0,
599 currentProp.indexOf(PROP_END) + 1);
600 current = current.substring(current.indexOf(currentProp) + 1);
601 String shortProp = prop.substring(PROP_START.length(),
602 prop.length() - 1);
603 keywordMap.put(new Integer(currentPosition), shortProp);
604 newPattern = replace(prop, shortProp, newPattern);
605 }
606 }
607
608 newPattern = replaceMetaChars(newPattern);
609
610
611
612 newPattern = util.substitute("s/" + MULTIPLE_SPACES_REGEXP +"/" + MULTIPLE_SPACES_REGEXP + "/g", newPattern);
613 newPattern = replace(PATTERN_WILDCARD, REGEXP_DEFAULT_WILDCARD, newPattern);
614
615
616
617
618
619
620
621
622
623 Iterator iter = keywords.iterator();
624 while (iter.hasNext()) {
625 String keyword = (String) iter.next();
626 int index2 = newPattern.indexOf(keyword);
627 if (index2 > -1) {
628 keywordMap.put(new Integer(index2), keyword);
629 }
630 }
631
632
633 matchingKeywords.addAll(keywordMap.values());
634
635
636
637
638
639 String currentPattern = newPattern;
640 for (int i = 0;i<matchingKeywords.size();i++) {
641 String keyword = (String) matchingKeywords.get(i);
642
643 if (i == (matchingKeywords.size() - 1)) {
644 currentPattern = replace(keyword, GREEDY_GROUP, currentPattern);
645 } else if (TIMESTAMP.equals(keyword)) {
646 currentPattern = replace(keyword, "(" + timestampPatternText + ")", currentPattern);
647 } else {
648 currentPattern = replace(keyword, DEFAULT_GROUP, currentPattern);
649 }
650 }
651
652 regexp = currentPattern;
653 getLogger().debug("regexp is " + regexp);
654 }
655
656 /***
657 * Helper method that will globally replace a section of text
658 *
659 * @param pattern
660 * @param replacement
661 * @param input
662 *
663 * @return string
664 */
665 private String replace(String pattern, String replacement, String input) {
666 return util.substitute("s/" + Perl5Compiler.quotemeta(pattern) + "/"
667 + Perl5Compiler.quotemeta(replacement) + "/g", input);
668 }
669
670 /***
671 * Some perl5 characters may occur in the log file format.
672 * Escape these characters to prevent parsing errors.
673 *
674 * @param input
675 * @return string
676 */
677 private String replaceMetaChars(String input) {
678 input = replace("(", "//(", input);
679 input = replace(")", "//)", input);
680 input = replace("[", "//[", input);
681 input = replace("]", "//]", input);
682 input = replace("{", "//{", input);
683 input = replace("}", "//}", input);
684 input = replace("#", "//#", input);
685 input = replace("/", "///", input);
686 return input;
687 }
688
689 /***
690 * Convert a keyword-to-values map to a LoggingEvent
691 *
692 * @param fieldMap
693 * @param exception
694 *
695 * @return logging event
696 */
697 private LoggingEvent convertToEvent(Map fieldMap, String[] exception) {
698 if (fieldMap == null) {
699 return null;
700 }
701
702
703 if (!fieldMap.containsKey(LOGGER)) {
704 fieldMap.put(LOGGER, "Unknown");
705 }
706 if (exception == null) {
707 exception = emptyException;
708 }
709
710 Logger logger = null;
711 long timeStamp = 0L;
712 String level = null;
713 String threadName = null;
714 Object message = null;
715 String ndc = null;
716 String className = null;
717 String methodName = null;
718 String eventFileName = null;
719 String lineNumber = null;
720 Hashtable properties = new Hashtable();
721
722 logger = Logger.getLogger((String) fieldMap.remove(LOGGER));
723
724 if ((dateFormat != null) && fieldMap.containsKey(TIMESTAMP)) {
725 try {
726 timeStamp = dateFormat.parse((String) fieldMap.remove(TIMESTAMP))
727 .getTime();
728 } catch (Exception e) {
729 e.printStackTrace();
730 }
731 }
732
733 if (timeStamp == 0L) {
734 timeStamp = System.currentTimeMillis();
735 }
736
737 level = (String) fieldMap.remove(LEVEL);
738 Level levelImpl = (level == null ? Level.DEBUG : Level.toLevel(level.trim()));
739
740 threadName = (String) fieldMap.remove(THREAD);
741
742 message = fieldMap.remove(MESSAGE);
743 if (message == null) {
744 message = "";
745 }
746
747 ndc = (String) fieldMap.remove(NDC);
748
749 className = (String) fieldMap.remove(CLASS);
750
751 methodName = (String) fieldMap.remove(METHOD);
752
753 eventFileName = (String) fieldMap.remove(FILE);
754
755 lineNumber = (String) fieldMap.remove(LINE);
756
757 properties.put(Constants.HOSTNAME_KEY, host);
758 properties.put(Constants.APPLICATION_KEY, path);
759 properties.put(Constants.RECEIVER_NAME_KEY, getName());
760
761
762 properties.putAll(fieldMap);
763
764 LocationInfo info = null;
765
766 if ((eventFileName != null) || (className != null) || (methodName != null)
767 || (lineNumber != null)) {
768 info = new LocationInfo(eventFileName, className, methodName, lineNumber);
769 } else {
770 info = LocationInfo.NA_LOCATION_INFO;
771 }
772
773 LoggingEvent event = new LoggingEvent(null,
774 logger, timeStamp, levelImpl, message,
775 threadName,
776 new ThrowableInformation(exception),
777 ndc,
778 info,
779 properties);
780
781 return event;
782 }
783
784 public static void main(String[] args) {
785
786
787
788
789
790
791
792
793
794
795
796
797
798 }
799
800 /***
801 * Close the reader.
802 */
803 public void shutdown() {
804 try {
805 if (reader != null) {
806 reader.close();
807 reader = null;
808 }
809 } catch (IOException ioe) {
810 ioe.printStackTrace();
811 }
812 }
813
814 /***
815 * Read and process the log file.
816 */
817 public void activateOptions() {
818 Runnable runnable = new Runnable() {
819 public void run() {
820 initialize();
821 while (reader == null) {
822 getLogger().info("attempting to load file: " + getFileURL());
823 try {
824 reader = new InputStreamReader(new URL(getFileURL()).openStream());
825 } catch (FileNotFoundException fnfe) {
826 getLogger().info("file not available - will try again in 10 seconds");
827 synchronized (this) {
828 try {
829 wait(10000);
830 } catch (InterruptedException ie) {}
831 }
832 } catch (IOException ioe) {
833 getLogger().warn("unable to load file", ioe);
834 return;
835 }
836 }
837 try {
838 BufferedReader bufferedReader = new BufferedReader(reader);
839 createPattern();
840 do {
841 getLogger().debug("tailing file: " + tailing);
842 process(bufferedReader);
843 try {
844 synchronized (this) {
845 wait(2000);
846 }
847 } catch (InterruptedException ie) {
848 }
849 } while (tailing);
850
851 } catch (IOException ioe) {
852
853 getLogger().info("stream closed");
854 }
855 getLogger().debug("processing " + fileURL + " complete");
856 shutdown();
857 }
858 };
859 if(useCurrentThread) {
860 runnable.run();
861 }else {
862 new Thread(runnable, "LogFilePatternReceiver-"+getName()).start();
863 }
864 }
865 }