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 }
调用该函数即可。