snowater

會當凌絕頂 少壯不努力老大徒傷悲 寶劍鋒從磨礪出梅花香自苦寒來

导航

JAVA 实现tail -f 日志文件监控功能

工具:

1 <dependency>
2     <groupId>commons-io</groupId>
3     <artifactId>commons-io</artifactId>
4     <version>2.4</version>
5 </dependency>

定义接口

 1 package com.snow.tailer;
 2 
 3 public interface TailerListener {
 4     /**
 5      * The tailer will call this method during construction,
 6      * giving the listener a method of stopping the tailer.
 7      * @param tailer the tailer.
 8      */
 9     void init(Tailer tailer);
10 
11     /**
12      * This method is called if the tailed file is not found.
13      * <p>
14      * <b>Note:</b> this is called from the tailer thread.
15      */
16     void fileNotFound();
17 
18     /**
19      * Called if a file rotation is detected.
20      *
21      * This method is called before the file is reopened, and fileNotFound may
22      * be called if the new file has not yet been created.
23      * <p>
24      * <b>Note:</b> this is called from the tailer thread.
25      */
26     void fileRotated();
27 
28     /**
29      * Handles a line from a Tailer.
30      * <p>
31      * <b>Note:</b> this is called from the tailer thread.
32      * @param line the line.
33      */
34     void handle(String line);
35 
36     /**
37      * Handles an Exception .
38      * <p>
39      * <b>Note:</b> this is called from the tailer thread.
40      * @param ex the exception.
41      */
42     void handle(Exception ex);
43 }

接口实现

 1 package com.snow.tailer;
 2 
 3 public class TailerListenerAdapter implements TailerListener {
 4     /**
 5      * The tailer will call this method during construction,
 6      * giving the listener a method of stopping the tailer.
 7      * @param tailer the tailer.
 8      */
 9     public void init(Tailer tailer) {
10     }
11 
12     /**
13      * This method is called if the tailed file is not found.
14      */
15     public void fileNotFound() {
16     }
17 
18     /**
19      * Called if a file rotation is detected.
20      *
21      * This method is called before the file is reopened, and fileNotFound may
22      * be called if the new file has not yet been created.
23      */
24     public void fileRotated() {
25     }
26 
27     /**
28      * Handles a line from a Tailer.
29      * @param line the line.
30      */
31     public void handle(String line) {
32     }
33 
34     /**
35      * Handles an Exception .
36      * @param ex the exception.
37      */
38     public void handle(Exception ex) {
39     }
40 
41 }

定义Tailer.java

  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 package com.snow.tailer;
 18 
 19 import org.apache.commons.io.FileUtils;
 20 import org.apache.commons.io.IOUtils;
 21 import org.slf4j.Logger;
 22 import org.slf4j.LoggerFactory;
 23 
 24 import java.io.File;
 25 import java.io.FileNotFoundException;
 26 import java.io.IOException;
 27 import java.io.RandomAccessFile;
 28 
 29 /**
 30  * Simple implementation of the unix "tail -f" functionality.
 31  * <p>
 32  * <h2>1. Create a TailerListener implementation</h3>
 33  * <p>
 34  * First you need to create a {@link TailerListener} implementation
 35  * ({@link TailerListenerAdapter} is provided for convenience so that you don't have to
 36  * implement every method).
 37  * </p>
 38  *
 39  * <p>For example:</p>
 40  * <pre>
 41  *  public class MyTailerListener extends TailerListenerAdapter {
 42  *      public void handle(String line) {
 43  *          System.out.println(line);
 44  *      }
 45  *  }
 46  * </pre>
 47  * 
 48  * <h2>2. Using a Tailer</h2>
 49  *
 50  * You can create and use a Tailer in one of three ways:
 51  * <ul>
 52  *   <li>Using one of the static helper methods:
 53  *     <ul>
 54  *       <li>{@link Tailer#create(File, TailerListener)}</li>
 55  *       <li>{@link Tailer#create(File, TailerListener, long)}</li>
 56  *       <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li>
 57  *     </ul>
 58  *   </li>
 59  *   <li>Using an {@link java.util.concurrent.Executor}</li>
 60  *   <li>Using an {@link Thread}</li>
 61  * </ul>
 62  *
 63  * An example of each of these is shown below.
 64  * 
 65  * <h3>2.1 Using the static helper method</h3>
 66  *
 67  * <pre>
 68  *      TailerListener listener = new MyTailerListener();
 69  *      Tailer tailer = Tailer.create(file, listener, delay);
 70  * </pre>
 71  *      
 72  * <h3>2.2 Use an Executor</h3>
 73  * 
 74  * <pre>
 75  *      TailerListener listener = new MyTailerListener();
 76  *      Tailer tailer = new Tailer(file, listener, delay);
 77  *
 78  *      // stupid executor impl. for demo purposes
 79  *      Executor executor = new Executor() {
 80  *          public void execute(Runnable command) {
 81  *              command.run();
 82  *           }
 83  *      };
 84  *
 85  *      executor.execute(tailer);
 86  * </pre>
 87  *      
 88  *      
 89  * <h3>2.3 Use a Thread</h3>
 90  * <pre>
 91  *      TailerListener listener = new MyTailerListener();
 92  *      Tailer tailer = new Tailer(file, listener, delay);
 93  *      Thread thread = new Thread(tailer);
 94  *      thread.setDaemon(true); // optional
 95  *      thread.start();
 96  * </pre>
 97  *
 98  * <h2>3. Stop Tailing</h3>
 99  * <p>Remember to stop the tailer when you have done with it:</p>
100  * <pre>
101  *      tailer.stop();
102  * </pre>
103  *
104  * @see TailerListener
105  * @see TailerListenerAdapter
106  * @version $Id: Tailer.java 1348698 2012-06-11 01:09:58Z ggregory $
107  * @since 2.0
108  */
109 public class Tailer implements Runnable {
110     private static final Logger logger = LoggerFactory.getLogger(Tailer.class);
111 
112     private static final int DEFAULT_DELAY_MILLIS = 1000;
113 
114     private static final String RAF_MODE = "r";
115 
116     private static final int DEFAULT_BUFSIZE = 4096;
117 
118     /**
119      * Buffer on top of RandomAccessFile.
120      */
121     private final byte inbuf[];
122 
123     /**
124      * The file which will be tailed.
125      */
126     private final File file;
127 
128     /**
129      * The amount of time to wait for the file to be updated.
130      */
131     private final long delayMillis;
132 
133     /**
134      * Whether to tail from the end or start of file
135      */
136     private final boolean end;
137 
138     /**
139      * The listener to notify of events when tailing.
140      */
141     private final TailerListener listener;
142 
143     /**
144      * Whether to close and reopen the file whilst waiting for more input.
145      */
146     private final boolean reOpen;
147 
148     /**
149      * The tailer will run as long as this value is true.
150      */
151     private volatile boolean run = true;
152     private volatile boolean resetFilePositionIfOverwrittenWithTheSameLength = false;
153 
154     /**
155      * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s.
156      * @param file The file to follow.
157      * @param listener the TailerListener to use.
158      */
159     public Tailer(File file, TailerListener listener) {
160         this(file, listener, DEFAULT_DELAY_MILLIS);
161     }
162 
163     /**
164      * Creates a Tailer for the given file, starting from the beginning.
165      * @param file the file to follow.
166      * @param listener the TailerListener to use.
167      * @param delayMillis the delay between checks of the file for new content in milliseconds.
168      */
169     public Tailer(File file, TailerListener listener, long delayMillis) {
170         this(file, listener, delayMillis, false);
171     }
172 
173     /**
174      * Creates a Tailer for the given file, with a delay other than the default 1.0s.
175      * @param file the file to follow.
176      * @param listener the TailerListener to use.
177      * @param delayMillis the delay between checks of the file for new content in milliseconds.
178      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
179      */
180     public Tailer(File file, TailerListener listener, long delayMillis, boolean end) {
181         this(file, listener, delayMillis, end, DEFAULT_BUFSIZE);
182         logger.info("Tailer inited from customer class.");
183     }
184 
185     /**
186      * Creates a Tailer for the given file, with a delay other than the default 1.0s.
187      * @param file the file to follow.
188      * @param listener the TailerListener to use.
189      * @param delayMillis the delay between checks of the file for new content in milliseconds.
190      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
191      * @param reOpen if true, close and reopen the file between reading chunks 
192      */
193     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) {
194         this(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE);
195     }
196 
197     /**
198      * Creates a Tailer for the given file, with a specified buffer size.
199      * @param file the file to follow.
200      * @param listener the TailerListener to use.
201      * @param delayMillis the delay between checks of the file for new content in milliseconds.
202      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
203      * @param bufSize Buffer size
204      */
205     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) {
206         this(file, listener, delayMillis, end, false, bufSize);
207     }
208 
209     /**
210      * Creates a Tailer for the given file, with a specified buffer size.
211      * @param file the file to follow.
212      * @param listener the TailerListener to use.
213      * @param delayMillis the delay between checks of the file for new content in milliseconds.
214      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
215      * @param reOpen if true, close and reopen the file between reading chunks 
216      * @param bufSize Buffer size
217      */
218     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, int bufSize) {
219         this.file = file;
220         this.delayMillis = delayMillis;
221         this.end = end;
222 
223         this.inbuf = new byte[bufSize];
224 
225         // Save and prepare the listener
226         this.listener = listener;
227         listener.init(this);
228         this.reOpen = reOpen;
229     }
230 
231     /**
232      * Creates and starts a Tailer for the given file.
233      * 
234      * @param file the file to follow.
235      * @param listener the TailerListener to use.
236      * @param delayMillis the delay between checks of the file for new content in milliseconds.
237      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
238      * @param bufSize buffer size.
239      * @return The new tailer
240      */
241     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) {
242         Tailer tailer = new Tailer(file, listener, delayMillis, end, bufSize);
243         Thread thread = new Thread(tailer);
244         thread.setDaemon(true);
245         thread.start();
246         return tailer;
247     }
248 
249     /**
250      * Creates and starts a Tailer for the given file.
251      * 
252      * @param file the file to follow.
253      * @param listener the TailerListener to use.
254      * @param delayMillis the delay between checks of the file for new content in milliseconds.
255      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
256      * @param reOpen whether to close/reopen the file between chunks
257      * @param bufSize buffer size.
258      * @return The new tailer
259      */
260     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, int bufSize) {
261         Tailer tailer = new Tailer(file, listener, delayMillis, end, reOpen, bufSize);
262         Thread thread = new Thread(tailer);
263         thread.setDaemon(true);
264         thread.start();
265         return tailer;
266     }
267 
268     /**
269      * Creates and starts a Tailer for the given file with default buffer size.
270      * 
271      * @param file the file to follow.
272      * @param listener the TailerListener to use.
273      * @param delayMillis the delay between checks of the file for new content in milliseconds.
274      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
275      * @return The new tailer
276      */
277     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end) {
278         return create(file, listener, delayMillis, end, DEFAULT_BUFSIZE);
279     }
280 
281     /**
282      * Creates and starts a Tailer for the given file with default buffer size.
283      * 
284      * @param file the file to follow.
285      * @param listener the TailerListener to use.
286      * @param delayMillis the delay between checks of the file for new content in milliseconds.
287      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
288      * @param reOpen whether to close/reopen the file between chunks
289      * @return The new tailer
290      */
291     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) {
292         return create(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE);
293     }
294 
295     /**
296      * Creates and starts a Tailer for the given file, starting at the beginning of the file
297      * 
298      * @param file the file to follow.
299      * @param listener the TailerListener to use.
300      * @param delayMillis the delay between checks of the file for new content in milliseconds.
301      * @return The new tailer
302      */
303     public static Tailer create(File file, TailerListener listener, long delayMillis) {
304         return create(file, listener, delayMillis, false);
305     }
306 
307     /**
308      * Creates and starts a Tailer for the given file, starting at the beginning of the file
309      * with the default delay of 1.0s
310      * 
311      * @param file the file to follow.
312      * @param listener the TailerListener to use.
313      * @return The new tailer
314      */
315     public static Tailer create(File file, TailerListener listener) {
316         return create(file, listener, DEFAULT_DELAY_MILLIS, false);
317     }
318 
319     /**
320      * Return the file.
321      *
322      * @return the file
323      */
324     public File getFile() {
325         return file;
326     }
327 
328     /**
329      * Return the delay in milliseconds.
330      *
331      * @return the delay in milliseconds.
332      */
333     public long getDelay() {
334         return delayMillis;
335     }
336 
337     /**
338      * Follows changes in the file, calling the TailerListener's handle method for each new line.
339      */
340     @Override
341     public void run() {
342         RandomAccessFile reader = null;
343         try {
344             long last = 0; // The last time the file was checked for changes
345             long position = 0; // position within the file
346             // Open the file
347             while (run && reader == null) {
348                 try {
349                     reader = new RandomAccessFile(file, RAF_MODE);
350                 } catch (FileNotFoundException e) {
351                     listener.fileNotFound();
352                 }
353 
354                 if (reader == null) {
355                     try {
356                         Thread.sleep(delayMillis);
357                     } catch (InterruptedException e) {
358                     }
359                 } else {
360                     // The current position in the file
361                     position = end ? file.length() : 0;
362                     last = file.lastModified();
363                     reader.seek(position);
364                 }
365             }
366 
367             while (run) {
368 
369                 boolean newer = FileUtils.isFileNewer(file, last); // IO-279, must be done first
370 
371                 // Check the file length to see if it was rotated
372                 long length = file.length();
373 
374                 if (length < position) {
375                     logger.info(String.format("rotated, legth=%s, position=%s", length, position));
376                     // File was rotated
377                     listener.fileRotated();
378 
379                     // Reopen the reader after rotation
380                     try {
381                         // Ensure that the old file is closed iff we re-open it successfully
382                         RandomAccessFile save = reader;
383                         reader = new RandomAccessFile(file, RAF_MODE);
384                         position = 0;
385                         // close old file explicitly rather than relying on GC picking up previous
386                         // RAF
387                         IOUtils.closeQuietly(save);
388                     } catch (FileNotFoundException e) {
389                         // in this case we continue to use the previous reader and position values
390                         listener.fileNotFound();
391                     }
392                     continue;
393                 } else {
394 
395                     // File was not rotated
396 
397                     // See if the file needs to be read again
398                     if (length > position) {
399 
400                         // The file has more content than it did last time
401                         position = readLines(reader);
402                         last = file.lastModified();
403 
404                     } else if (newer) {
405                         logger.info(String.format("newer, legth=%s, position=%s", length, position));
406                         if (resetFilePositionIfOverwrittenWithTheSameLength) {
407                             /*
408                              * This can happen if the file is truncated or overwritten with the exact same length of
409                              * information. In cases like this, the file position needs to be reset
410                              */
411                             position = 0;
412                             reader.seek(position); // cannot be null here
413 
414                             // Now we can read new lines
415                             position = readLines(reader);
416                         }
417                         last = file.lastModified();
418                     }
419                 }
420                 if (reOpen) {
421                     IOUtils.closeQuietly(reader);
422                 }
423                 try {
424                     Thread.sleep(delayMillis);
425                 } catch (InterruptedException e) {
426                 }
427                 if (run && reOpen) {
428                     reader = new RandomAccessFile(file, RAF_MODE);
429                     reader.seek(position);
430                     logger.info(String.format("reopen, legth=%s, position=%s", length, position));
431                 }
432             }
433 
434         } catch (Exception e) {
435 
436             listener.handle(e);
437 
438         } finally {
439             IOUtils.closeQuietly(reader);
440         }
441     }
442 
443     /**
444      * Allows the tailer to complete its current loop and return.
445      */
446     public void stop() {
447         this.run = false;
448     }
449 
450     /**
451      * Read new lines.
452      *
453      * @param reader The file to read
454      * @return The new position after the lines have been read
455      * @throws IOException if an I/O error occurs.
456      */
457     private long readLines(RandomAccessFile reader) throws IOException {
458         StringBuilder sb = new StringBuilder();
459 
460         long pos = reader.getFilePointer();
461         long rePos = pos; // position to re-read
462 
463         int num;
464         boolean seenCR = false;
465         while (run && ((num = reader.read(inbuf)) != -1)) {
466             for (int i = 0; i < num; i++) {
467                 byte ch = inbuf[i];
468                 switch (ch) {
469                     case '\n':
470                         seenCR = false; // swallow CR before LF
471                         listener.handle(sb.toString());
472                         sb.setLength(0);
473                         rePos = pos + i + 1;
474                         break;
475                     case '\r':
476                         if (seenCR) {
477                             sb.append('\r');
478                         }
479                         seenCR = true;
480                         break;
481                     default:
482                         if (seenCR) {
483                             seenCR = false; // swallow final CR
484                             listener.handle(sb.toString());
485                             sb.setLength(0);
486                             rePos = pos + i + 1;
487                         }
488                         sb.append((char) ch); // add character, not its ascii value
489                 }
490             }
491 
492             pos = reader.getFilePointer();
493         }
494 
495         reader.seek(rePos); // Ensure we can re-read if necessary
496         return rePos;
497     }
498 
499 }

封装使用函数:

 1 /**
 2  * @param inputFile  监控文件
 3  * @param sleepInterval  当文件没有日志时sleep间隔
 4  */
 5 private static void monitor(String inputFile, int sleepInterval) {
 6     TailerListener listener = new TailerListenerAdapter() {
 7         @Override
 8         public void handle(String line) {
 9             if (++count % 100000 == 0) {
10                 log.info("{} lines sent since the program up.", count);
11             }
12             if (StringUtils.isEmpty(line)) {
13                 log.warn("should not read empty line.");
14                 return;
15             } else {
16                 // do something ... 
17             }
18         }
19     };
20     Tailer tailer = new Tailer(new File(inputFile), listener, sleepInterval, true);
21     tailer.run();
22 }

调用该函数即可。

 

posted on 2017-09-27 20:13  snowater  阅读(7055)  评论(0编辑  收藏  举报