工具:
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