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  * 
 
 41  *  public class MyTailerListener extends TailerListenerAdapter { 
 42  *      public void handle(String line) { 
 43  *          System.out.println(line); 
 44  *      } 
 45  *  } 
 46  * 

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 *

 
 68  *      TailerListener listener = new MyTailerListener(); 
 69  *      Tailer tailer = Tailer.create(file, listener, delay); 
 70  * 

71 *
72 * <h3>2.2 Use an Executor</h3>
73 *
74 *

 
 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  * 

87 *
88 *
89 * <h3>2.3 Use a Thread</h3>
90 *

 
 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  * 

97 *
98 * <h2>3. Stop Tailing</h3>
99 * <p>Remember to stop the tailer when you have done with it:</p>
100 *

 
101  *      tailer.stop(); 
102  * 

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 }

调用该函数即可。

 

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/11358.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论