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