001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input; 018 019import static org.apache.commons.io.IOUtils.CR; 020import static org.apache.commons.io.IOUtils.EOF; 021import static org.apache.commons.io.IOUtils.LF; 022 023import java.io.ByteArrayOutputStream; 024import java.io.File; 025import java.io.FileNotFoundException; 026import java.io.IOException; 027import java.io.RandomAccessFile; 028import java.nio.charset.Charset; 029 030import org.apache.commons.io.FileUtils; 031import org.apache.commons.io.IOUtils; 032 033/** 034 * Simple implementation of the unix "tail -f" functionality. 035 * 036 * <h2>1. Create a TailerListener implementation</h2> 037 * <p> 038 * First you need to create a {@link TailerListener} implementation 039 * ({@link TailerListenerAdapter} is provided for convenience so that you don't have to 040 * implement every method). 041 * </p> 042 * 043 * <p>For example:</p> 044 * <pre> 045 * public class MyTailerListener extends TailerListenerAdapter { 046 * public void handle(String line) { 047 * System.out.println(line); 048 * } 049 * }</pre> 050 * 051 * <h2>2. Using a Tailer</h2> 052 * 053 * <p> 054 * You can create and use a Tailer in one of three ways: 055 * </p> 056 * <ul> 057 * <li>Using one of the static helper methods: 058 * <ul> 059 * <li>{@link Tailer#create(File, TailerListener)}</li> 060 * <li>{@link Tailer#create(File, TailerListener, long)}</li> 061 * <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li> 062 * </ul> 063 * </li> 064 * <li>Using an {@link java.util.concurrent.Executor}</li> 065 * <li>Using an {@link Thread}</li> 066 * </ul> 067 * 068 * <p> 069 * An example of each of these is shown below. 070 * </p> 071 * 072 * <h3>2.1 Using the static helper method</h3> 073 * 074 * <pre> 075 * TailerListener listener = new MyTailerListener(); 076 * Tailer tailer = Tailer.create(file, listener, delay);</pre> 077 * 078 * <h3>2.2 Using an Executor</h3> 079 * 080 * <pre> 081 * TailerListener listener = new MyTailerListener(); 082 * Tailer tailer = new Tailer(file, listener, delay); 083 * 084 * // stupid executor impl. for demo purposes 085 * Executor executor = new Executor() { 086 * public void execute(Runnable command) { 087 * command.run(); 088 * } 089 * }; 090 * 091 * executor.execute(tailer); 092 * </pre> 093 * 094 * 095 * <h3>2.3 Using a Thread</h3> 096 * <pre> 097 * TailerListener listener = new MyTailerListener(); 098 * Tailer tailer = new Tailer(file, listener, delay); 099 * Thread thread = new Thread(tailer); 100 * thread.setDaemon(true); // optional 101 * thread.start();</pre> 102 * 103 * <h2>3. Stopping a Tailer</h2> 104 * <p>Remember to stop the tailer when you have done with it:</p> 105 * <pre> 106 * tailer.stop(); 107 * </pre> 108 * 109 * <h2>4. Interrupting a Tailer</h2> 110 * <p>You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}. 111 * </p> 112 * <pre> 113 * thread.interrupt(); 114 * </pre> 115 * <p> 116 * If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}. 117 * </p> 118 * <p> 119 * The file is read using the default charset; this can be overridden if necessary. 120 * </p> 121 * @see TailerListener 122 * @see TailerListenerAdapter 123 * @since 2.0 124 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()} 125 */ 126public class Tailer implements Runnable { 127 128 private static final int DEFAULT_DELAY_MILLIS = 1000; 129 130 private static final String RAF_MODE = "r"; 131 132 // The default charset used for reading files 133 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); 134 135 /** 136 * Buffer on top of RandomAccessFile. 137 */ 138 private final byte[] inbuf; 139 140 /** 141 * The file which will be tailed. 142 */ 143 private final File file; 144 145 /** 146 * The character set that will be used to read the file. 147 */ 148 private final Charset charset; 149 150 /** 151 * The amount of time to wait for the file to be updated. 152 */ 153 private final long delayMillis; 154 155 /** 156 * Whether to tail from the end or start of file 157 */ 158 private final boolean end; 159 160 /** 161 * The listener to notify of events when tailing. 162 */ 163 private final TailerListener listener; 164 165 /** 166 * Whether to close and reopen the file whilst waiting for more input. 167 */ 168 private final boolean reOpen; 169 170 /** 171 * The tailer will run as long as this value is true. 172 */ 173 private volatile boolean run = true; 174 175 /** 176 * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s. 177 * @param file The file to follow. 178 * @param listener the TailerListener to use. 179 */ 180 public Tailer(final File file, final TailerListener listener) { 181 this(file, listener, DEFAULT_DELAY_MILLIS); 182 } 183 184 /** 185 * Creates a Tailer for the given file, starting from the beginning. 186 * @param file the file to follow. 187 * @param listener the TailerListener to use. 188 * @param delayMillis the delay between checks of the file for new content in milliseconds. 189 */ 190 public Tailer(final File file, final TailerListener listener, final long delayMillis) { 191 this(file, listener, delayMillis, false); 192 } 193 194 /** 195 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 196 * @param file the file to follow. 197 * @param listener the TailerListener to use. 198 * @param delayMillis the delay between checks of the file for new content in milliseconds. 199 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 200 */ 201 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) { 202 this(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE); 203 } 204 205 /** 206 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 207 * @param file the file to follow. 208 * @param listener the TailerListener to use. 209 * @param delayMillis the delay between checks of the file for new content in milliseconds. 210 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 211 * @param reOpen if true, close and reopen the file between reading chunks 212 */ 213 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 214 final boolean reOpen) { 215 this(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE); 216 } 217 218 /** 219 * Creates a Tailer for the given file, with a specified buffer size. 220 * @param file the file to follow. 221 * @param listener the TailerListener to use. 222 * @param delayMillis the delay between checks of the file for new content in milliseconds. 223 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 224 * @param bufSize Buffer size 225 */ 226 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 227 final int bufSize) { 228 this(file, listener, delayMillis, end, false, bufSize); 229 } 230 231 /** 232 * Creates a Tailer for the given file, with a specified buffer size. 233 * @param file the file to follow. 234 * @param listener the TailerListener to use. 235 * @param delayMillis the delay between checks of the file for new content in milliseconds. 236 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 237 * @param reOpen if true, close and reopen the file between reading chunks 238 * @param bufSize Buffer size 239 */ 240 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 241 final boolean reOpen, final int bufSize) { 242 this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 243 } 244 245 /** 246 * Creates a Tailer for the given file, with a specified buffer size. 247 * @param file the file to follow. 248 * @param charset the Charset to be used for reading the file 249 * @param listener the TailerListener to use. 250 * @param delayMillis the delay between checks of the file for new content in milliseconds. 251 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 252 * @param reOpen if true, close and reopen the file between reading chunks 253 * @param bufSize Buffer size 254 */ 255 public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis, 256 final boolean end, final boolean reOpen 257 , final int bufSize) { 258 this.file = file; 259 this.delayMillis = delayMillis; 260 this.end = end; 261 262 this.inbuf = IOUtils.byteArray(bufSize); 263 264 // Save and prepare the listener 265 this.listener = listener; 266 listener.init(this); 267 this.reOpen = reOpen; 268 this.charset = charset; 269 } 270 271 /** 272 * Creates and starts a Tailer for the given file. 273 * 274 * @param file the file to follow. 275 * @param listener the TailerListener to use. 276 * @param delayMillis the delay between checks of the file for new content in milliseconds. 277 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 278 * @param bufSize buffer size. 279 * @return The new tailer 280 */ 281 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 282 final boolean end, final int bufSize) { 283 return create(file, listener, delayMillis, end, false, bufSize); 284 } 285 286 /** 287 * Creates and starts a Tailer for the given file. 288 * 289 * @param file the file to follow. 290 * @param listener the TailerListener to use. 291 * @param delayMillis the delay between checks of the file for new content in milliseconds. 292 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 293 * @param reOpen whether to close/reopen the file between chunks 294 * @param bufSize buffer size. 295 * @return The new tailer 296 */ 297 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 298 final boolean end, final boolean reOpen, 299 final int bufSize) { 300 return create(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 301 } 302 303 /** 304 * Creates and starts a Tailer for the given file. 305 * 306 * @param file the file to follow. 307 * @param charset the character set to use for reading the file 308 * @param listener the TailerListener to use. 309 * @param delayMillis the delay between checks of the file for new content in milliseconds. 310 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 311 * @param reOpen whether to close/reopen the file between chunks 312 * @param bufSize buffer size. 313 * @return The new tailer 314 */ 315 public static Tailer create(final File file, final Charset charset, final TailerListener listener, 316 final long delayMillis, final boolean end, final boolean reOpen 317 ,final int bufSize) { 318 final Tailer tailer = new Tailer(file, charset, listener, delayMillis, end, reOpen, bufSize); 319 final Thread thread = new Thread(tailer); 320 thread.setDaemon(true); 321 thread.start(); 322 return tailer; 323 } 324 325 /** 326 * Creates and starts a Tailer for the given file with default buffer size. 327 * 328 * @param file the file to follow. 329 * @param listener the TailerListener to use. 330 * @param delayMillis the delay between checks of the file for new content in milliseconds. 331 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 332 * @return The new tailer 333 */ 334 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 335 final boolean end) { 336 return create(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE); 337 } 338 339 /** 340 * Creates and starts a Tailer for the given file with default buffer size. 341 * 342 * @param file the file to follow. 343 * @param listener the TailerListener to use. 344 * @param delayMillis the delay between checks of the file for new content in milliseconds. 345 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 346 * @param reOpen whether to close/reopen the file between chunks 347 * @return The new tailer 348 */ 349 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 350 final boolean end, final boolean reOpen) { 351 return create(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE); 352 } 353 354 /** 355 * Creates and starts a Tailer for the given file, starting at the beginning of the file 356 * 357 * @param file the file to follow. 358 * @param listener the TailerListener to use. 359 * @param delayMillis the delay between checks of the file for new content in milliseconds. 360 * @return The new tailer 361 */ 362 public static Tailer create(final File file, final TailerListener listener, final long delayMillis) { 363 return create(file, listener, delayMillis, false); 364 } 365 366 /** 367 * Creates and starts a Tailer for the given file, starting at the beginning of the file 368 * with the default delay of 1.0s 369 * 370 * @param file the file to follow. 371 * @param listener the TailerListener to use. 372 * @return The new tailer 373 */ 374 public static Tailer create(final File file, final TailerListener listener) { 375 return create(file, listener, DEFAULT_DELAY_MILLIS, false); 376 } 377 378 /** 379 * Return the file. 380 * 381 * @return the file 382 */ 383 public File getFile() { 384 return file; 385 } 386 387 /** 388 * Gets whether to keep on running. 389 * 390 * @return whether to keep on running. 391 * @since 2.5 392 */ 393 protected boolean getRun() { 394 return run; 395 } 396 397 /** 398 * Return the delay in milliseconds. 399 * 400 * @return the delay in milliseconds. 401 */ 402 public long getDelay() { 403 return delayMillis; 404 } 405 406 /** 407 * Follows changes in the file, calling the TailerListener's handle method for each new line. 408 */ 409 @Override 410 public void run() { 411 RandomAccessFile reader = null; 412 try { 413 long last = 0; // The last time the file was checked for changes 414 long position = 0; // position within the file 415 // Open the file 416 while (getRun() && reader == null) { 417 try { 418 reader = new RandomAccessFile(file, RAF_MODE); 419 } catch (final FileNotFoundException e) { 420 listener.fileNotFound(); 421 } 422 if (reader == null) { 423 Thread.sleep(delayMillis); 424 } else { 425 // The current position in the file 426 position = end ? file.length() : 0; 427 last = FileUtils.lastModified(file); 428 reader.seek(position); 429 } 430 } 431 while (getRun()) { 432 final boolean newer = FileUtils.isFileNewer(file, last); // IO-279, must be done first 433 // Check the file length to see if it was rotated 434 final long length = file.length(); 435 if (length < position) { 436 // File was rotated 437 listener.fileRotated(); 438 // Reopen the reader after rotation ensuring that the old file is closed iff we re-open it 439 // successfully 440 try (RandomAccessFile save = reader) { 441 reader = new RandomAccessFile(file, RAF_MODE); 442 // At this point, we're sure that the old file is rotated 443 // Finish scanning the old file and then we'll start with the new one 444 try { 445 readLines(save); 446 } catch (final IOException ioe) { 447 listener.handle(ioe); 448 } 449 position = 0; 450 } catch (final FileNotFoundException e) { 451 // in this case we continue to use the previous reader and position values 452 listener.fileNotFound(); 453 Thread.sleep(delayMillis); 454 } 455 continue; 456 } 457 // File was not rotated 458 // See if the file needs to be read again 459 if (length > position) { 460 // The file has more content than it did last time 461 position = readLines(reader); 462 last = FileUtils.lastModified(file); 463 } else if (newer) { 464 /* 465 * This can happen if the file is truncated or overwritten with the exact same length of 466 * information. In cases like this, the file position needs to be reset 467 */ 468 position = 0; 469 reader.seek(position); // cannot be null here 470 471 // Now we can read new lines 472 position = readLines(reader); 473 last = FileUtils.lastModified(file); 474 } 475 if (reOpen && reader != null) { 476 reader.close(); 477 } 478 Thread.sleep(delayMillis); 479 if (getRun() && reOpen) { 480 reader = new RandomAccessFile(file, RAF_MODE); 481 reader.seek(position); 482 } 483 } 484 } catch (final InterruptedException e) { 485 Thread.currentThread().interrupt(); 486 listener.handle(e); 487 } catch (final Exception e) { 488 listener.handle(e); 489 } finally { 490 try { 491 if (reader != null) { 492 reader.close(); 493 } 494 } catch (final IOException e) { 495 listener.handle(e); 496 } 497 stop(); 498 } 499 } 500 501 /** 502 * Allows the tailer to complete its current loop and return. 503 */ 504 public void stop() { 505 this.run = false; 506 } 507 508 /** 509 * Read new lines. 510 * 511 * @param reader The file to read 512 * @return The new position after the lines have been read 513 * @throws java.io.IOException if an I/O error occurs. 514 */ 515 private long readLines(final RandomAccessFile reader) throws IOException { 516 try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) { 517 long pos = reader.getFilePointer(); 518 long rePos = pos; // position to re-read 519 int num; 520 boolean seenCR = false; 521 while (getRun() && ((num = reader.read(inbuf)) != EOF)) { 522 for (int i = 0; i < num; i++) { 523 final byte ch = inbuf[i]; 524 switch (ch) { 525 case LF: 526 seenCR = false; // swallow CR before LF 527 listener.handle(new String(lineBuf.toByteArray(), charset)); 528 lineBuf.reset(); 529 rePos = pos + i + 1; 530 break; 531 case CR: 532 if (seenCR) { 533 lineBuf.write(CR); 534 } 535 seenCR = true; 536 break; 537 default: 538 if (seenCR) { 539 seenCR = false; // swallow final CR 540 listener.handle(new String(lineBuf.toByteArray(), charset)); 541 lineBuf.reset(); 542 rePos = pos + i + 1; 543 } 544 lineBuf.write(ch); 545 } 546 } 547 pos = reader.getFilePointer(); 548 } 549 550 reader.seek(rePos); // Ensure we can re-read if necessary 551 552 if (listener instanceof TailerListenerAdapter) { 553 ((TailerListenerAdapter) listener).endOfFileReached(); 554 } 555 556 return rePos; 557 } 558 } 559}