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 overriden if necessary</p> 115 * @see TailerListener 116 * @see TailerListenerAdapter 117 * @version $Id: Tailer.java 1714076 2015-11-12 16:06:41Z krosenvold $ 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 private static final int DEFAULT_BUFSIZE = 4096; 128 129 // The default charset used for reading files 130 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); 131 132 /** 133 * Buffer on top of RandomAccessFile. 134 */ 135 private final byte inbuf[]; 136 137 /** 138 * The file which will be tailed. 139 */ 140 private final File file; 141 142 /** 143 * The character set that will be used to read the file. 144 */ 145 private final Charset cset; 146 147 /** 148 * The amount of time to wait for the file to be updated. 149 */ 150 private final long delayMillis; 151 152 /** 153 * Whether to tail from the end or start of file 154 */ 155 private final boolean end; 156 157 /** 158 * The listener to notify of events when tailing. 159 */ 160 private final TailerListener listener; 161 162 /** 163 * Whether to close and reopen the file whilst waiting for more input. 164 */ 165 private final boolean reOpen; 166 167 /** 168 * The tailer will run as long as this value is true. 169 */ 170 private volatile boolean run = true; 171 172 /** 173 * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s. 174 * @param file The file to follow. 175 * @param listener the TailerListener to use. 176 */ 177 public Tailer(final File file, final TailerListener listener) { 178 this(file, listener, DEFAULT_DELAY_MILLIS); 179 } 180 181 /** 182 * Creates a Tailer for the given file, starting from the beginning. 183 * @param file the file to follow. 184 * @param listener the TailerListener to use. 185 * @param delayMillis the delay between checks of the file for new content in milliseconds. 186 */ 187 public Tailer(final File file, final TailerListener listener, final long delayMillis) { 188 this(file, listener, delayMillis, false); 189 } 190 191 /** 192 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 193 * @param file the file to follow. 194 * @param listener the TailerListener to use. 195 * @param delayMillis the delay between checks of the file for new content in milliseconds. 196 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 197 */ 198 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) { 199 this(file, listener, delayMillis, end, DEFAULT_BUFSIZE); 200 } 201 202 /** 203 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 204 * @param file the file to follow. 205 * @param listener the TailerListener to use. 206 * @param delayMillis the delay between checks of the file for new content in milliseconds. 207 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 208 * @param reOpen if true, close and reopen the file between reading chunks 209 */ 210 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 211 final boolean reOpen) { 212 this(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE); 213 } 214 215 /** 216 * Creates a Tailer for the given file, with a specified buffer size. 217 * @param file the file to follow. 218 * @param listener the TailerListener to use. 219 * @param delayMillis the delay between checks of the file for new content in milliseconds. 220 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 221 * @param bufSize Buffer size 222 */ 223 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 224 final int bufSize) { 225 this(file, listener, delayMillis, end, false, bufSize); 226 } 227 228 /** 229 * Creates a Tailer for the given file, with a specified buffer size. 230 * @param file the file to follow. 231 * @param listener the TailerListener to use. 232 * @param delayMillis the delay between checks of the file for new content in milliseconds. 233 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 234 * @param reOpen if true, close and reopen the file between reading chunks 235 * @param bufSize Buffer size 236 */ 237 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 238 final boolean reOpen, final int bufSize) { 239 this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 240 } 241 242 /** 243 * Creates a Tailer for the given file, with a specified buffer size. 244 * @param file the file to follow. 245 * @param cset the Charset to be used for reading the file 246 * @param listener the TailerListener to use. 247 * @param delayMillis the delay between checks of the file for new content in milliseconds. 248 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 249 * @param reOpen if true, close and reopen the file between reading chunks 250 * @param bufSize Buffer size 251 */ 252 public Tailer(final File file, final Charset cset, final TailerListener listener, final long delayMillis, 253 final boolean end, final boolean reOpen 254 , final int bufSize) { 255 this.file = file; 256 this.delayMillis = delayMillis; 257 this.end = end; 258 259 this.inbuf = new byte[bufSize]; 260 261 // Save and prepare the listener 262 this.listener = listener; 263 listener.init(this); 264 this.reOpen = reOpen; 265 this.cset = cset; 266 } 267 268 /** 269 * Creates and starts a Tailer for the given file. 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 * @param bufSize buffer size. 276 * @return The new tailer 277 */ 278 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 279 final boolean end, final int bufSize) { 280 return create(file, listener, delayMillis, end, false, bufSize); 281 } 282 283 /** 284 * Creates and starts a Tailer for the given file. 285 * 286 * @param file the file to follow. 287 * @param listener the TailerListener to use. 288 * @param delayMillis the delay between checks of the file for new content in milliseconds. 289 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 290 * @param reOpen whether to close/reopen the file between chunks 291 * @param bufSize buffer size. 292 * @return The new tailer 293 */ 294 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 295 final boolean end, final boolean reOpen, 296 final int bufSize) { 297 return create(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 298 } 299 300 /** 301 * Creates and starts a Tailer for the given file. 302 * 303 * @param file the file to follow. 304 * @param charset the character set to use for reading the file 305 * @param listener the TailerListener to use. 306 * @param delayMillis the delay between checks of the file for new content in milliseconds. 307 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 308 * @param reOpen whether to close/reopen the file between chunks 309 * @param bufSize buffer size. 310 * @return The new tailer 311 */ 312 public static Tailer create(final File file, final Charset charset, final TailerListener listener, 313 final long delayMillis, final boolean end, final boolean reOpen 314 ,final int bufSize) { 315 final Tailer tailer = new Tailer(file, charset, listener, delayMillis, end, reOpen, bufSize); 316 final Thread thread = new Thread(tailer); 317 thread.setDaemon(true); 318 thread.start(); 319 return tailer; 320 } 321 322 /** 323 * Creates and starts a Tailer for the given file with default buffer size. 324 * 325 * @param file the file to follow. 326 * @param listener the TailerListener to use. 327 * @param delayMillis the delay between checks of the file for new content in milliseconds. 328 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 329 * @return The new tailer 330 */ 331 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 332 final boolean end) { 333 return create(file, listener, delayMillis, end, DEFAULT_BUFSIZE); 334 } 335 336 /** 337 * Creates and starts a Tailer for the given file with default buffer size. 338 * 339 * @param file the file to follow. 340 * @param listener the TailerListener to use. 341 * @param delayMillis the delay between checks of the file for new content in milliseconds. 342 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 343 * @param reOpen whether to close/reopen the file between chunks 344 * @return The new tailer 345 */ 346 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 347 final boolean end, final boolean reOpen) { 348 return create(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE); 349 } 350 351 /** 352 * Creates and starts a Tailer for the given file, starting at the beginning of the file 353 * 354 * @param file the file to follow. 355 * @param listener the TailerListener to use. 356 * @param delayMillis the delay between checks of the file for new content in milliseconds. 357 * @return The new tailer 358 */ 359 public static Tailer create(final File file, final TailerListener listener, final long delayMillis) { 360 return create(file, listener, delayMillis, false); 361 } 362 363 /** 364 * Creates and starts a Tailer for the given file, starting at the beginning of the file 365 * with the default delay of 1.0s 366 * 367 * @param file the file to follow. 368 * @param listener the TailerListener to use. 369 * @return The new tailer 370 */ 371 public static Tailer create(final File file, final TailerListener listener) { 372 return create(file, listener, DEFAULT_DELAY_MILLIS, false); 373 } 374 375 /** 376 * Return the file. 377 * 378 * @return the file 379 */ 380 public File getFile() { 381 return file; 382 } 383 384 /** 385 * Gets whether to keep on running. 386 * 387 * @return whether to keep on running. 388 * @since 2.5 389 */ 390 protected boolean getRun() { 391 return run; 392 } 393 394 /** 395 * Return the delay in milliseconds. 396 * 397 * @return the delay in milliseconds. 398 */ 399 public long getDelay() { 400 return delayMillis; 401 } 402 403 /** 404 * Follows changes in the file, calling the TailerListener's handle method for each new line. 405 */ 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 435 try { 436 // Ensure that the old file is closed iff we re-open it successfully 437 final RandomAccessFile save = reader; 438 reader = new RandomAccessFile(file, RAF_MODE); 439 // At this point, we're sure that the old file is rotated 440 // Finish scanning the old file and then we'll start with the new one 441 try { 442 readLines(save); 443 } catch (IOException ioe) { 444 listener.handle(ioe); 445 } 446 position = 0; 447 // close old file explicitly rather than relying on GC picking up previous RAF 448 IOUtils.closeQuietly(save); 449 } catch (final FileNotFoundException e) { 450 // in this case we continue to use the previous reader and position values 451 listener.fileNotFound(); 452 } 453 continue; 454 } else { 455 // File was not rotated 456 // See if the file needs to be read again 457 if (length > position) { 458 // The file has more content than it did last time 459 position = readLines(reader); 460 last = file.lastModified(); 461 } else if (newer) { 462 /* 463 * This can happen if the file is truncated or overwritten with the exact same length of 464 * information. In cases like this, the file position needs to be reset 465 */ 466 position = 0; 467 reader.seek(position); // cannot be null here 468 469 // Now we can read new lines 470 position = readLines(reader); 471 last = file.lastModified(); 472 } 473 } 474 if (reOpen) { 475 IOUtils.closeQuietly(reader); 476 } 477 Thread.sleep(delayMillis); 478 if (getRun() && reOpen) { 479 reader = new RandomAccessFile(file, RAF_MODE); 480 reader.seek(position); 481 } 482 } 483 } catch (final InterruptedException e) { 484 Thread.currentThread().interrupt(); 485 stop(e); 486 } catch (final Exception e) { 487 stop(e); 488 } finally { 489 IOUtils.closeQuietly(reader); 490 } 491 } 492 493 /** 494 * Stops the tailer with an exception 495 * @param e The exception to send to listener 496 */ 497 private void stop(final Exception e) { 498 listener.handle(e); 499 stop(); 500 } 501 502 /** 503 * Allows the tailer to complete its current loop and return. 504 */ 505 public void stop() { 506 this.run = false; 507 } 508 509 /** 510 * Read new lines. 511 * 512 * @param reader The file to read 513 * @return The new position after the lines have been read 514 * @throws java.io.IOException if an I/O error occurs. 515 */ 516 private long readLines(final RandomAccessFile reader) throws IOException { 517 ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64); 518 long pos = reader.getFilePointer(); 519 long rePos = pos; // position to re-read 520 int num; 521 boolean seenCR = false; 522 while (getRun() && ((num = reader.read(inbuf)) != EOF)) { 523 for (int i = 0; i < num; i++) { 524 final byte ch = inbuf[i]; 525 switch (ch) { 526 case '\n': 527 seenCR = false; // swallow CR before LF 528 listener.handle(new String(lineBuf.toByteArray(), cset)); 529 lineBuf.reset(); 530 rePos = pos + i + 1; 531 break; 532 case '\r': 533 if (seenCR) { 534 lineBuf.write('\r'); 535 } 536 seenCR = true; 537 break; 538 default: 539 if (seenCR) { 540 seenCR = false; // swallow final CR 541 listener.handle(new String(lineBuf.toByteArray(), cset)); 542 lineBuf.reset(); 543 rePos = pos + i + 1; 544 } 545 lineBuf.write(ch); 546 } 547 } 548 pos = reader.getFilePointer(); 549 } 550 IOUtils.closeQuietly(lineBuf); // not strictly necessary 551 reader.seek(rePos); // Ensure we can re-read if necessary 552 553 if (listener instanceof TailerListenerAdapter) { 554 ((TailerListenerAdapter) listener).endOfFileReached(); 555 } 556 557 return rePos; 558 } 559 560}