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