java实现tail_JAVA 实现tail -f 日志文件监控功能详解

本文详细介绍了如何使用Java实现类似于Unix命令`tail -f`的日志文件监控功能。通过创建`TailerListener`实现监听器,然后利用静态帮助方法、Executor或Thread来启动`Tailer`。文章提供了不同构造方法的示例,展示了如何根据需求调整延迟时间、从文件开头或结尾开始跟踪,以及是否在读取文件之间关闭和重新打开文件。
摘要由CSDN通过智能技术生成

1 /*

2 * Licensed to the Apache Software Foundation (ASF) under one or more3 * contributor license agreements. See the NOTICE file distributed with4 * this work for additional information regarding copyright ownership.5 * The ASF licenses this file to You under the Apache License, Version 2.06 * (the "License"); you may not use this file except in compliance with7 * the License. You may obtain a copy of the License at8 *9 *http://www.apache.org/licenses/LICENSE-2.0

10 *11 * Unless required by applicable law or agreed to in writing, software12 * 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 and15 * limitations under the License.16 */

17 packagecom.snow.tailer;18

19 importorg.apache.commons.io.FileUtils;20 importorg.apache.commons.io.IOUtils;21 importorg.slf4j.Logger;22 importorg.slf4j.LoggerFactory;23

24 importjava.io.File;25 importjava.io.FileNotFoundException;26 importjava.io.IOException;27 importjava.io.RandomAccessFile;28

29 /**

30 * Simple implementation of the unix "tail -f" functionality.31 *

32 *

1. Create a TailerListener implementation33 *

34 * First you need to create a {@linkTailerListener} implementation35 * ({@linkTailerListenerAdapter} is provided for convenience so that you don't have to36 * implement every method).37 *

38 *39 *

For example:

40 *
41 *  public class MyTailerListener extends TailerListenerAdapter {42 *      public void handle(String line) {43 *          System.out.println(line);44 *      }45 *  }46 * 
47 *48 *

2. Using a Tailer

49 *50 * You can create and use a Tailer in one of three ways:51 *
  • 52 *
  • Using one of the static helper methods:53 *
    • 54 *
    • {@linkTailer#create(File, TailerListener)}55 *
    • {@linkTailer#create(File, TailerListener, long)}56 *
    • {@linkTailer#create(File, TailerListener, long, boolean)}57 *
    58 * 59 *
  • Using an {@linkjava.util.concurrent.Executor}60 *
  • Using an {@linkThread}61 *
62 *63 * An example of each of these is shown below.64 *65 *

2.1 Using the static helper method

66 *67 *
68 *      TailerListener listener = new MyTailerListener();69 *      Tailer tailer = Tailer.create(file, listener, delay);70 * 
71 *72 *

2.2 Use an Executor

73 *74 *
75 *      TailerListener listener = new MyTailerListener();76 *      Tailer tailer = new Tailer(file, listener, delay);77 *78 *      // stupid executor impl. for demo purposes79 *      Executor executor = new Executor() {80 *          public void execute(Runnable command) {81 *              command.run();82 *           }83 *      };84 *85 *      executor.execute(tailer);86 * 
87 *88 *89 *

2.3 Use a Thread

90 *
91 *      TailerListener listener = new MyTailerListener();92 *      Tailer tailer = new Tailer(file, listener, delay);93 *      Thread thread = new Thread(tailer);94 *      thread.setDaemon(true); // optional95 *      thread.start();96 * 
97 *98 *

3. Stop Tailing99 *

Remember to stop the tailer when you have done with it:

100 *
101 *      tailer.stop();102 * 
103 *104 *@seeTailerListener105 *@seeTailerListenerAdapter106 *@version$Id: Tailer.java 1348698 2012-06-11 01:09:58Z ggregory $107 *@since2.0108 */

109 public class Tailer implementsRunnable {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 byteinbuf[];122

123 /**

124 * The file which will be tailed.125 */

126 private finalFile file;127

128 /**

129 * The amount of time to wait for the file to be updated.130 */

131 private final longdelayMillis;132

133 /**

134 * Whether to tail from the end or start of file135 */

136 private final booleanend;137

138 /**

139 * The listener to notify of events when tailing.140 */

141 private finalTailerListener listener;142

143 /**

144 * Whether to close and reopen the file whilst waiting for more input.145 */

146 private final booleanreOpen;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 *@paramfile The file to follow.157 *@paramlistener the TailerListener to use.158 */

159 publicTailer(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 *@paramfile the file to follow.166 *@paramlistener the TailerListener to use.167 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.168 */

169 public Tailer(File file, TailerListener listener, longdelayMillis) {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 *@paramfile the file to follow.176 *@paramlistener the TailerListener to use.177 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.178 *@paramend 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, booleanend) {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 *@paramfile the file to follow.188 *@paramlistener the TailerListener to use.189 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.190 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.191 *@paramreOpen if true, close and reopen the file between reading chunks192 */

193 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, booleanreOpen) {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 *@paramfile the file to follow.200 *@paramlistener the TailerListener to use.201 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.202 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.203 *@parambufSize Buffer size204 */

205 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, intbufSize) {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 *@paramfile the file to follow.212 *@paramlistener the TailerListener to use.213 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.214 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.215 *@paramreOpen if true, close and reopen the file between reading chunks216 *@parambufSize Buffer size217 */

218 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, intbufSize) {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 *@paramfile the file to follow.235 *@paramlistener the TailerListener to use.236 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.237 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.238 *@parambufSize buffer size.239 *@returnThe new tailer240 */

241 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, intbufSize) {242 Tailer tailer = newTailer(file, listener, delayMillis, end, bufSize);243 Thread thread = newThread(tailer);244 thread.setDaemon(true);245 thread.start();246 returntailer;247 }248

249 /**

250 * Creates and starts a Tailer for the given file.251 *252 *@paramfile the file to follow.253 *@paramlistener the TailerListener to use.254 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.255 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.256 *@paramreOpen whether to close/reopen the file between chunks257 *@parambufSize buffer size.258 *@returnThe new tailer259 */

260 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, intbufSize) {261 Tailer tailer = newTailer(file, listener, delayMillis, end, reOpen, bufSize);262 Thread thread = newThread(tailer);263 thread.setDaemon(true);264 thread.start();265 returntailer;266 }267

268 /**

269 * Creates and starts a Tailer for the given file with default buffer size.270 *271 *@paramfile the file to follow.272 *@paramlistener the TailerListener to use.273 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.274 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.275 *@returnThe new tailer276 */

277 public static Tailer create(File file, TailerListener listener, long delayMillis, booleanend) {278 returncreate(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 *@paramfile the file to follow.285 *@paramlistener the TailerListener to use.286 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.287 *@paramend Set to true to tail from the end of the file, false to tail from the beginning of the file.288 *@paramreOpen whether to close/reopen the file between chunks289 *@returnThe new tailer290 */

291 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, booleanreOpen) {292 returncreate(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 file297 *298 *@paramfile the file to follow.299 *@paramlistener the TailerListener to use.300 *@paramdelayMillis the delay between checks of the file for new content in milliseconds.301 *@returnThe new tailer302 */

303 public static Tailer create(File file, TailerListener listener, longdelayMillis) {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 file309 * with the default delay of 1.0s310 *311 *@paramfile the file to follow.312 *@paramlistener the TailerListener to use.313 *@returnThe new tailer314 */

315 public staticTailer create(File file, TailerListener listener) {316 return create(file, listener, DEFAULT_DELAY_MILLIS, false);317 }318

319 /**

320 * Return the file.321 *322 *@returnthe file323 */

324 publicFile getFile() {325 returnfile;326 }327

328 /**

329 * Return the delay in milliseconds.330 *331 *@returnthe delay in milliseconds.332 */

333 public longgetDelay() {334 returndelayMillis;335 }336

337 /**

338 * Follows changes in the file, calling the TailerListener's handle method for each new line.339 */

340 @Override341 public voidrun() {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 file346 //Open the file

347 while (run && reader == null) {348 try{349 reader = newRandomAccessFile(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 first370

371 //Check the file length to see if it was rotated

372 long length =file.length();373

374 if (length

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 = newRandomAccessFile(file, RAF_MODE);384 position = 0;385 //close old file explicitly rather than relying on GC picking up previous386 //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 rotated396

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 of409 * information. In cases like this, the file position needs to be reset410 */

411 position = 0;412 reader.seek(position); //cannot be null here413

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 = newRandomAccessFile(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 voidstop() {447 this.run = false;448 }449

450 /**

451 * Read new lines.452 *453 *@paramreader The file to read454 *@returnThe new position after the lines have been read455 *@throwsIOException if an I/O error occurs.456 */

457 private long readLines(RandomAccessFile reader) throwsIOException {458 StringBuilder sb = newStringBuilder();459

460 long pos =reader.getFilePointer();461 long rePos = pos; //position to re-read

462

463 intnum;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 returnrePos;497 }498

499 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值