zoukankan      html  css  js  c++  java
  • JAVA 实现tail -f 日志文件监控功能

    工具:

    1 <dependency>
    2     <groupId>commons-io</groupId>
    3     <artifactId>commons-io</artifactId>
    4     <version>2.4</version>
    5 </dependency>

    定义接口

     1 package com.snow.tailer;
     2 
     3 public interface TailerListener {
     4     /**
     5      * The tailer will call this method during construction,
     6      * giving the listener a method of stopping the tailer.
     7      * @param tailer the tailer.
     8      */
     9     void init(Tailer tailer);
    10 
    11     /**
    12      * This method is called if the tailed file is not found.
    13      * <p>
    14      * <b>Note:</b> this is called from the tailer thread.
    15      */
    16     void fileNotFound();
    17 
    18     /**
    19      * Called if a file rotation is detected.
    20      *
    21      * This method is called before the file is reopened, and fileNotFound may
    22      * be called if the new file has not yet been created.
    23      * <p>
    24      * <b>Note:</b> this is called from the tailer thread.
    25      */
    26     void fileRotated();
    27 
    28     /**
    29      * Handles a line from a Tailer.
    30      * <p>
    31      * <b>Note:</b> this is called from the tailer thread.
    32      * @param line the line.
    33      */
    34     void handle(String line);
    35 
    36     /**
    37      * Handles an Exception .
    38      * <p>
    39      * <b>Note:</b> this is called from the tailer thread.
    40      * @param ex the exception.
    41      */
    42     void handle(Exception ex);
    43 }

    接口实现

     1 package com.snow.tailer;
     2 
     3 public class TailerListenerAdapter implements TailerListener {
     4     /**
     5      * The tailer will call this method during construction,
     6      * giving the listener a method of stopping the tailer.
     7      * @param tailer the tailer.
     8      */
     9     public void init(Tailer tailer) {
    10     }
    11 
    12     /**
    13      * This method is called if the tailed file is not found.
    14      */
    15     public void fileNotFound() {
    16     }
    17 
    18     /**
    19      * Called if a file rotation is detected.
    20      *
    21      * This method is called before the file is reopened, and fileNotFound may
    22      * be called if the new file has not yet been created.
    23      */
    24     public void fileRotated() {
    25     }
    26 
    27     /**
    28      * Handles a line from a Tailer.
    29      * @param line the line.
    30      */
    31     public void handle(String line) {
    32     }
    33 
    34     /**
    35      * Handles an Exception .
    36      * @param ex the exception.
    37      */
    38     public void handle(Exception ex) {
    39     }
    40 
    41 }

    定义Tailer.java

      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * 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 and
     15  * limitations under the License.
     16  */
     17 package com.snow.tailer;
     18 
     19 import org.apache.commons.io.FileUtils;
     20 import org.apache.commons.io.IOUtils;
     21 import org.slf4j.Logger;
     22 import org.slf4j.LoggerFactory;
     23 
     24 import java.io.File;
     25 import java.io.FileNotFoundException;
     26 import java.io.IOException;
     27 import java.io.RandomAccessFile;
     28 
     29 /**
     30  * Simple implementation of the unix "tail -f" functionality.
     31  * <p>
     32  * <h2>1. Create a TailerListener implementation</h3>
     33  * <p>
     34  * First you need to create a {@link TailerListener} implementation
     35  * ({@link TailerListenerAdapter} is provided for convenience so that you don't have to
     36  * implement every method).
     37  * </p>
     38  *
     39  * <p>For example:</p>
     40  * <pre>
     41  *  public class MyTailerListener extends TailerListenerAdapter {
     42  *      public void handle(String line) {
     43  *          System.out.println(line);
     44  *      }
     45  *  }
     46  * </pre>
     47  * 
     48  * <h2>2. Using a Tailer</h2>
     49  *
     50  * You can create and use a Tailer in one of three ways:
     51  * <ul>
     52  *   <li>Using one of the static helper methods:
     53  *     <ul>
     54  *       <li>{@link Tailer#create(File, TailerListener)}</li>
     55  *       <li>{@link Tailer#create(File, TailerListener, long)}</li>
     56  *       <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li>
     57  *     </ul>
     58  *   </li>
     59  *   <li>Using an {@link java.util.concurrent.Executor}</li>
     60  *   <li>Using an {@link Thread}</li>
     61  * </ul>
     62  *
     63  * An example of each of these is shown below.
     64  * 
     65  * <h3>2.1 Using the static helper method</h3>
     66  *
     67  * <pre>
     68  *      TailerListener listener = new MyTailerListener();
     69  *      Tailer tailer = Tailer.create(file, listener, delay);
     70  * </pre>
     71  *      
     72  * <h3>2.2 Use an Executor</h3>
     73  * 
     74  * <pre>
     75  *      TailerListener listener = new MyTailerListener();
     76  *      Tailer tailer = new Tailer(file, listener, delay);
     77  *
     78  *      // stupid executor impl. for demo purposes
     79  *      Executor executor = new Executor() {
     80  *          public void execute(Runnable command) {
     81  *              command.run();
     82  *           }
     83  *      };
     84  *
     85  *      executor.execute(tailer);
     86  * </pre>
     87  *      
     88  *      
     89  * <h3>2.3 Use a Thread</h3>
     90  * <pre>
     91  *      TailerListener listener = new MyTailerListener();
     92  *      Tailer tailer = new Tailer(file, listener, delay);
     93  *      Thread thread = new Thread(tailer);
     94  *      thread.setDaemon(true); // optional
     95  *      thread.start();
     96  * </pre>
     97  *
     98  * <h2>3. Stop Tailing</h3>
     99  * <p>Remember to stop the tailer when you have done with it:</p>
    100  * <pre>
    101  *      tailer.stop();
    102  * </pre>
    103  *
    104  * @see TailerListener
    105  * @see TailerListenerAdapter
    106  * @version $Id: Tailer.java 1348698 2012-06-11 01:09:58Z ggregory $
    107  * @since 2.0
    108  */
    109 public class Tailer implements Runnable {
    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 byte inbuf[];
    122 
    123     /**
    124      * The file which will be tailed.
    125      */
    126     private final File file;
    127 
    128     /**
    129      * The amount of time to wait for the file to be updated.
    130      */
    131     private final long delayMillis;
    132 
    133     /**
    134      * Whether to tail from the end or start of file
    135      */
    136     private final boolean end;
    137 
    138     /**
    139      * The listener to notify of events when tailing.
    140      */
    141     private final TailerListener listener;
    142 
    143     /**
    144      * Whether to close and reopen the file whilst waiting for more input.
    145      */
    146     private final boolean reOpen;
    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      * @param file The file to follow.
    157      * @param listener the TailerListener to use.
    158      */
    159     public Tailer(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      * @param file the file to follow.
    166      * @param listener the TailerListener to use.
    167      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    168      */
    169     public Tailer(File file, TailerListener listener, long delayMillis) {
    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      * @param file the file to follow.
    176      * @param listener the TailerListener to use.
    177      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    178      * @param end 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, boolean end) {
    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      * @param file the file to follow.
    188      * @param listener the TailerListener to use.
    189      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    190      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    191      * @param reOpen if true, close and reopen the file between reading chunks 
    192      */
    193     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) {
    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      * @param file the file to follow.
    200      * @param listener the TailerListener to use.
    201      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    202      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    203      * @param bufSize Buffer size
    204      */
    205     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) {
    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      * @param file the file to follow.
    212      * @param listener the TailerListener to use.
    213      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    214      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    215      * @param reOpen if true, close and reopen the file between reading chunks 
    216      * @param bufSize Buffer size
    217      */
    218     public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, int bufSize) {
    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      * @param file the file to follow.
    235      * @param listener the TailerListener to use.
    236      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    237      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    238      * @param bufSize buffer size.
    239      * @return The new tailer
    240      */
    241     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) {
    242         Tailer tailer = new Tailer(file, listener, delayMillis, end, bufSize);
    243         Thread thread = new Thread(tailer);
    244         thread.setDaemon(true);
    245         thread.start();
    246         return tailer;
    247     }
    248 
    249     /**
    250      * Creates and starts a Tailer for the given file.
    251      * 
    252      * @param file the file to follow.
    253      * @param listener the TailerListener to use.
    254      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    255      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    256      * @param reOpen whether to close/reopen the file between chunks
    257      * @param bufSize buffer size.
    258      * @return The new tailer
    259      */
    260     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, int bufSize) {
    261         Tailer tailer = new Tailer(file, listener, delayMillis, end, reOpen, bufSize);
    262         Thread thread = new Thread(tailer);
    263         thread.setDaemon(true);
    264         thread.start();
    265         return tailer;
    266     }
    267 
    268     /**
    269      * Creates and starts a Tailer for the given file with default buffer size.
    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      * @return The new tailer
    276      */
    277     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end) {
    278         return create(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      * @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      * @return The new tailer
    290      */
    291     public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) {
    292         return create(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 file
    297      * 
    298      * @param file the file to follow.
    299      * @param listener the TailerListener to use.
    300      * @param delayMillis the delay between checks of the file for new content in milliseconds.
    301      * @return The new tailer
    302      */
    303     public static Tailer create(File file, TailerListener listener, long delayMillis) {
    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 file
    309      * with the default delay of 1.0s
    310      * 
    311      * @param file the file to follow.
    312      * @param listener the TailerListener to use.
    313      * @return The new tailer
    314      */
    315     public static Tailer create(File file, TailerListener listener) {
    316         return create(file, listener, DEFAULT_DELAY_MILLIS, false);
    317     }
    318 
    319     /**
    320      * Return the file.
    321      *
    322      * @return the file
    323      */
    324     public File getFile() {
    325         return file;
    326     }
    327 
    328     /**
    329      * Return the delay in milliseconds.
    330      *
    331      * @return the delay in milliseconds.
    332      */
    333     public long getDelay() {
    334         return delayMillis;
    335     }
    336 
    337     /**
    338      * Follows changes in the file, calling the TailerListener's handle method for each new line.
    339      */
    340     @Override
    341     public void run() {
    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 file
    346             // Open the file
    347             while (run && reader == null) {
    348                 try {
    349                     reader = new RandomAccessFile(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 first
    370 
    371                 // Check the file length to see if it was rotated
    372                 long length = file.length();
    373 
    374                 if (length < position) {
    375                     logger.info(String.format("rotated, legth=%s, position=%s", length, position));
    376                     // File was rotated
    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 = new RandomAccessFile(file, RAF_MODE);
    384                         position = 0;
    385                         // close old file explicitly rather than relying on GC picking up previous
    386                         // 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 rotated
    396 
    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 of
    409                              * information. In cases like this, the file position needs to be reset
    410                              */
    411                             position = 0;
    412                             reader.seek(position); // cannot be null here
    413 
    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 = new RandomAccessFile(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 void stop() {
    447         this.run = false;
    448     }
    449 
    450     /**
    451      * Read new lines.
    452      *
    453      * @param reader The file to read
    454      * @return The new position after the lines have been read
    455      * @throws IOException if an I/O error occurs.
    456      */
    457     private long readLines(RandomAccessFile reader) throws IOException {
    458         StringBuilder sb = new StringBuilder();
    459 
    460         long pos = reader.getFilePointer();
    461         long rePos = pos; // position to re-read
    462 
    463         int num;
    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 '
    ':
    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 '
    ':
    476                         if (seenCR) {
    477                             sb.append('
    ');
    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         return rePos;
    497     }
    498 
    499 }

    封装使用函数:

     1 /**
     2  * @param inputFile  监控文件
     3  * @param sleepInterval  当文件没有日志时sleep间隔
     4  */
     5 private static void monitor(String inputFile, int sleepInterval) {
     6     TailerListener listener = new TailerListenerAdapter() {
     7         @Override
     8         public void handle(String line) {
     9             if (++count % 100000 == 0) {
    10                 log.info("{} lines sent since the program up.", count);
    11             }
    12             if (StringUtils.isEmpty(line)) {
    13                 log.warn("should not read empty line.");
    14                 return;
    15             } else {
    16                 // do something ... 
    17             }
    18         }
    19     };
    20     Tailer tailer = new Tailer(new File(inputFile), listener, sleepInterval, true);
    21     tailer.run();
    22 }

    调用该函数即可。

  • 相关阅读:
    redis 使用 get 命令读取 bitmap 类型的数据
    如何用vue-router为每个路由配置各自的title
    vue定义全局变量和全局方法
    后端分布式系列:分布式存储-HDFS 与 GFS 的设计差异
    Android Studio(十二):打包多个发布渠道的apk文件
    Android中使用lambda表达式
    Android教程-03 常见布局的总结
    程序员的业余项目
    Android Studio(十一):代码混淆及打包apk
    win10提示 磁盘包含不是“PARTITION_BASIC_DATA_GUID"类型的分区
  • 原文地址:https://www.cnblogs.com/snowater/p/7603611.html
Copyright © 2011-2022 走看看