org.hrva.capture.LogTail.java Source code

Java tutorial

Introduction

Here is the source code for org.hrva.capture.LogTail.java

Source

/*
 * The HRT Project.
 * This work is licensed under the 
 * Creative Commons Attribution-NonCommercial 3.0 Unported License. 
 * To view a copy of this license, 
 * visit http://creativecommons.org/licenses/by-nc/3.0/ 
 * or send a letter to 
 * Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.
 */
package org.hrva.capture;

import java.io.*;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

/**
 * Tail a rapidly growing log file and push an HRT Feeds to the CouchDB.
 *
 * <p>This is both a main program with a command-line interface, as well as
 * object that can be used to tail a log file. </p>
 *
 * <p>Typical use case</p>
 * <code><pre>
 *     LogTail lt = new LogTail();
 *     lt.tail( "/path/to/some.log", "extract.txt" );
 *     CouchPush cp= new CouchPush()
 *     cp.open();
 *     cp.push_feed( "extract.txt" );
 *     System.out.print( "Created "+cp.id );
 * </pre></code>
 *
 * <p>At the command line, it might look like this.</p> 
 * <code><pre>
 * java -cp LogTail/dist/LogTail.jar org.hrva.capture.LogTail -o extract.txt /path/to/some.log
 * java -cp LogTail/dist/LogTail.jar org.hrva.capture.CouchPush -f extract.txt 
 * </pre></code>
 * 
 * @author slott
 */
public class LogTail {

    /** Properties for this application. */
    Properties global = new Properties();

    /** Output file name. */
    @Option(name = "-o", usage = "Output file name.")
    String extract_filename = "hrtrtf.txt";

    /** Immediate Push option. */
    @Option(name = "-f", usage = "Do an immediate feed push.")
    boolean immediate = false;

    /** Verbose debugging. */
    @Option(name = "-v", usage = "Vebose logging")
    boolean verbose = false;

    /** Command-line Arguments. */
    @Argument
    List<String> arguments = new ArrayList<String>();

    /** Logger. */
    final Log logger = LogFactory.getLog(LogTail.class);

    /**
     * Command-line program to tail a log and then push file to the HRT couch
     * DB.
     * <p>All this does is read properties and invoke run_main</p>
     *
     * @param args arguments
     */
    public static void main(String[] args) {
        Log log = LogFactory.getLog(LogTail.class);
        File prop_file = new File("hrtail.properties");
        Properties config = new Properties();
        try {
            config.load(new FileInputStream(prop_file));
        } catch (IOException ex) {
            log.warn("Can't find " + prop_file.getName(), ex);
            try {
                log.debug(prop_file.getCanonicalPath());
            } catch (IOException ex1) {
            }
        }
        LogTail lt = new LogTail(config);
        try {
            lt.run_main(args);
        } catch (CmdLineException ex1) {
            log.fatal("Invalid Options", ex1);
        } catch (MalformedURLException ex2) {
            log.fatal("Invalid CouchDB URL", ex2);
        } catch (IOException ex3) {
            log.fatal(ex3);
        }
    }

    /**
     * Build the LogTail instance. 
     * 
     * @param global The hrtail.properties file
     */
    public LogTail(Properties global) {
        super();
        this.global = global;
    }

    /**
     * Tails the log and (optionally) pushes a feed file.
     *
     * <ol> <li>Get cached status info.</li> <li>Tail Log</li>
     * <li>Update cached status info.</li>
     * <li>(optionally) Send to
     * couchdb.</li>  </ol>
     *
     * @param args the command line arguments
     * @throws CmdLineException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void run_main(String[] args) throws CmdLineException, FileNotFoundException, IOException {
        CmdLineParser parser = new CmdLineParser(this);
        parser.parseArgument(args);

        if (arguments.size() != 1) {
            throw new CmdLineException("Only one log file can be tailed");
        }
        for (String source : arguments) {
            String temp = tail(source, extract_filename);
            if (temp != null && immediate) {
                push_feed(temp);
            }
        }
    }

    /**
     * Tail the given file if the size has changed and return a temp filename.
     *
     * <p>This returns a temp filename if the log being tailed has changed.
     * </p>
     * 
     * <p>The supplied target filename is -- actually -- a format string.
     * The available value, <<tt>{0}</tt> is the sequence number
     * that's saved in the history cache.</p>
     *
     * @param source The log filename to tail
     * @param target A temporary filename into which to save the tail piece.
     * @return temp filename, if the file size changed; otherwise null
     * @throws FileNotFoundException
     * @throws IOException
     */
    public String tail(String source, String target) throws FileNotFoundException, IOException {
        // The resulting file name (or null if the log did not grow).
        String temp_name = null;

        // Open our last-time-we-looked file.
        String cache_file_name = global.getProperty("logtail.tail_status_filename", "logtail.history");
        String limit_str = global.getProperty("logtail.file_size_limit", "1m"); // 1 * 1024 * 1024;
        int limit;
        if (limit_str.endsWith("m") || limit_str.endsWith("M")) {
            limit = 1024 * 1024 * Integer.parseInt(limit_str.substring(0, limit_str.length() - 1));
        } else if (limit_str.endsWith("k") || limit_str.endsWith("K")) {
            limit = 1024 * Integer.parseInt(limit_str.substring(0, limit_str.length() - 1));
        } else {
            limit = Integer.parseInt(limit_str);
        }

        Properties state = get_state(cache_file_name);

        // Find the previous size and sequence number
        String prev_size_str = state.getProperty("size." + source, "0");
        long prev_size = Long.parseLong(prev_size_str);
        String seq_str = state.getProperty("seq." + source, "0");
        long sequence = Long.parseLong(seq_str);

        Object[] details = { source, target, seq_str, prev_size_str };
        logger.info(MessageFormat.format("Tailing {0} to {1}", details));
        logger.info(MessageFormat.format("Count {2}, Bytes {3}", details));
        sequence += 1;

        // Attempt to seek to the previous position
        long position = 0;
        File log_to_tail = new File(source);
        RandomAccessFile rdr = new RandomAccessFile(log_to_tail, "r");
        try {
            long current_size = rdr.length();
            if (current_size == prev_size) {
                // Same size.  Nothing more to do here.
                position = current_size;
            } else {
                // Changed size.  Either grew or was truncated.
                if (rdr.length() < prev_size) {
                    // Got truncated.  Read from beginning.
                    sequence = 0;
                    prev_size = 0;
                } else {
                    // Got bigger.  Read from where we left off.
                    rdr.seek(prev_size);
                }
                // Read to EOF or the limit.  
                // No reason to get greedy.
                int read_size;
                if (current_size - prev_size > limit) {
                    read_size = limit;
                    rdr.seek(current_size - limit);
                } else {
                    read_size = (int) (current_size - prev_size);
                }
                byte[] buffer = new byte[read_size];
                rdr.read(buffer);
                position = rdr.getFilePointer();

                // Write temp file
                Object[] args = { sequence };
                temp_name = MessageFormat.format(target, args);

                File extract = new File(temp_name);
                OutputStream wtr = new FileOutputStream(extract);
                wtr.write(buffer);
            }
        } finally {
            rdr.close();
        }

        // Update our private last-time-we-looked file.
        state.setProperty("size." + source, String.valueOf(position));
        state.setProperty("seq." + source, String.valueOf(sequence));
        save_state(cache_file_name, state);

        Object[] details2 = { source, target, seq_str, prev_size_str, String.valueOf(sequence),
                String.valueOf(position) };
        logger.info(MessageFormat.format("Count {4}, Bytes {5}", details2));

        return temp_name;
    }

    /**
     * Push the given file to the database server. This essentially runs the
     * CouchPush application.
     *
     * @param filename
     * @throws MalformedURLException
     * @throws IOException
     */
    public void push_feed(String filename) throws MalformedURLException, IOException {
        File attachment = new File(filename);
        CouchPush cp = new CouchPush(global);
        cp.open();
        cp.push_feed(attachment);
    }

    /**
     * Get the saved file size state.
     *
     * @param name Properties file into which the file sizes were saved.
     * @return Properties object with saved file sizes.
     */
    public Properties get_state(String name) {
        Properties state = new Properties();
        File cache_file = new File(name);
        if (cache_file.exists()) {
            InputStream istr;
            try {
                istr = new FileInputStream(cache_file);
                state.load(istr);
            } catch (FileNotFoundException ex) {
                logger.warn("No history " + name, ex);
            } catch (java.io.IOException ex) {
                logger.warn("Problems with history " + name, ex);
            }
        }
        return state;
    }

    /**
     * Save the file size for next time we're executed.
     *
     * @param name Properties file into which the file sizes are saved.
     * @param state Properties object to persist.
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void save_state(String name, Properties state) throws FileNotFoundException, IOException {
        OutputStream ostr = new FileOutputStream(name);
        state.store(ostr, "LogTail Cache");
    }
}