- Overview
-
Table of Contents
- J2SE: Standard Java
- Java Windows NT Services
- Apache Velocity
- Advanced J2SE
- Bytecode Instrumentation
- Dynamic Languages and the JVM
- J2SE 1.5.0: "Tiger"
- Java SE 6
- Java 7
- Core Computer Science Principles in Java (Data Structures)
- Annotations
- Java Generics
- Java New I/O
- Java Sound
- Java Applets
- JavaFX
- Java SE Threading
- Resource Management Using Semaphores
- Java Atomic Operations
- JavaTemplate Pages
- Executing Templates with the JtpExecutor
- Java Cryptography Extensions (JCE)
- Java Database Connectivity (JDBC) API
- Jakarta Commons - Net Class Library
- Jakarta Commons HttpClient
- Apache POI
- Regular Expressions
- JavaMail
-
Cool Tools
- Tailer Library
- JTailer: A GUI Tailer
- Tailer 2.0
- Server-based Log Tailing
- JoSQL
- JavaSRC Cache
- JavaSRC Cache: A Cache Example
- Java Scheduler
- DeJar
- FindInJar
- JLayer
- SAH (HTML) Parser
- HTMLDocument – Part I
- HTMLDocument – Part II
- Launch4j
- RXML
- Reflection-based Swing Tools (RTools Swing)
- RTools - Annotations
- JLaunch: Launching External Processes
- JLaunch GUI
- PDF JavaBean
- Ganymed SSH-2 for Java
- Building an Really Simple Syndication (RSS) Java App
- Embedding JavaScript in Java with Rhino
- Logging with Log4J
- Inside Swing
- Swing Components
- SwingX
- Swing Styled Documents
- Web Rendering in Java Swing Applications
- Java Look-and-Feel Graphics Repository
- Java Media Framework
- Quicktime for Java
- Media in Java Review 2008
- External Multimedia in Java
- Graphs and Charts
- Holiday Special: Electronic Greeting Card
- Media Framework: Presenter Application
- Standard Widget Toolkit
- JFace
- Java Performance Tuning
- J2EE Performance Tuning
- Caches and Pools
- Java Caching System
- EHCache
- Java Compression and Decompression
- Obfuscating Java Applications
- Continuous Integration
- Load Testing
- Tomcat Clustering
- High Scalability with Terracotta
- Troubleshooting Production Performance Issues
- Enterprise Java Testing
- Automated Unit Testing with JUnit and Ant
- Unit Testing: Tips From The Trenches
- Custom Ant Tasks
- Extensible Markup Language (XML)
- Java Web Technologies
- Web Frameworks
- Struts 2
- Wicket
- JavaServer Faces
- Distributed Programming / RMI
- Behavior Tracking Servlet Filter
- Servlet Filters
- Building a Robust Java Server
- J2EE: Enterprise Java
- Spring
- Spring 3
- Java Design Patterns
- Model-Driven Architecture
- Enterprise Messaging with ActiveMQ
- Event-Driven Architecture
- XDoclet
- Hibernate
- Developing Standalone Database Applications with Hypersonic DB
- Project Backup
- J2EE Project: Hands-On
- Enterprise Java Beans (EJB) 3.0
- Disaster Recovery
- Java Management Extensions (JMX)
- Service-Oriented Architecture
- Web Services
- RESTful Web Services
- Web Services with Apache CXF
- Atom Syndication
- Project: Building a Web Photo Gallery
- J2ME: Micro Java
- Specialized J2ME
- Optional Packages
- Other Java Technologies
- Derivatives and Competitors
- Java, Engineered for Integration
-
Additional Resources
- The World of Java Tools
- Building Java Applications with Ant
- Managing Java Build Lifecycles with Maven
- Acceptance Testing with FitNesse
- Source Control with Subversion
- Inversion of Control and Dependency Injection
- Certification
- Roadmap: Becoming an Enterprise Java Developer
- Roadmap: Becoming an Enterprise Java Developer in 2007
- The Business of Enterprise Software
- JavaOne 2006
- JavaOne 2007
- JavaOne 2008 Wrap-Up
- JavaOne 2009 Wrap-Up
- JavaOne 2010
- JavaOne 2011
- How to Survive in a Turbulent Job Market
- How to Hire the Best Talent
- Unified Modeling Language (UML)
- Cloud Computing
- Amazon EC2 and Java
- MongoDB
- Enterprise Java in 2008 and Beyond
- Predictions for 2018
Tailer Library
Last updated Mar 14, 2003.
You are probably familiar with the UNIX concept of tailing a file, or, more specifically, using the tail command with the –f option to following a running file. In case you haven't encountered it before: the purpose of a following tail is to watch the contents of a file as the file is being created. It's usually a log file; it is common, in the UNIX world, to follow a server process log file as it is starting, to ensure that the server starts properly.
As I am writing my next book about Java EE Performance Management, I was reminded of an article I wrote, describing how to convert a verbose garbage collection log to XML for better integration into a monitoring solution. However, there is a problem with parsing an entire file. It gives you good information after-the-fact, but it does not allow you to see things in real-time.
The solution to this problem is to build a log file tailer that can follow a running verbose garbage collection log file and fire events as new lines are written. But as I am always looking to write reusable code, I abstracted this functionality into a generic log file tailer class that I will later use to tail verbose garbage collection logs. For now, you can use this class and infrastructure to follow any text-based file.
Before diving into code, let's review the requirements for a tailer program. A tailer program must unobtrusively open a text file (so that the owner of the file can continue to write to it); wait for new lines to be written to it; and notify someone when new lines are added.
The next question is: how can we do this with Java? We are used to reading text files using readers, but those are meant to read a stream until completion and stop; we need another way. And that way is the RandomAccessFile class. Our strategy is to open a file as a RandomAccessFile in read-only mode, seek to the end of the file, and then watch the file. Unfortunately, there is no easy way to watch a file: it would be nice if a notification mechanism could tell us when the file is updated, but in lieu of that, we fall back to polling. On a fixed interval we check the length of the file. If it is greater than our file pointer, we seek to our file pointer, read until the end of the file, and then update the file pointer. Simple enough, no?
Listing 1 shows the code for the LogFileTailer class.
Listing 1. LogFileTailer.java
package com.javasrc.tuning.agent.logfile;
import java.io.*;
import java.util.*;
/**
* A log file tailer is designed to monitor a log file and send notifications
* when new lines are added to the log file. This class has a notification
* strategy similar to a SAX parser: implement the LogFileTailerListener interface,
* create a LogFileTailer to tail your log file, add yourself as a listener, and
* start the LogFileTailer. It is your job to interpret the results, build meaningful
* sets of data, etc. This tailer simply fires notifications containing new log file lines,
* one at a time.
*/
public class LogFileTailer extends Thread
{
/**
* How frequently to check for file changes; defaults to 5 seconds
*/
private long sampleInterval = 5000;
/**
* The log file to tail
*/
private File logfile;
/**
* Defines whether the log file tailer should include the entire contents
* of the exising log file or tail from the end of the file when the tailer starts
*/
private boolean startAtBeginning = false;
/**
* Is the tailer currently tailing?
*/
private boolean tailing = false;
/**
* Set of listeners
*/
private Set listeners = new HashSet();
/**
* Creates a new log file tailer that tails an existing file and checks the file for
* updates every 5000ms
*/
public LogFileTailer( File file )
{
this.logfile = file;
}
/**
* Creates a new log file tailer
*
* @param file The file to tail
* @param sampleInterval How often to check for updates to the log file (default = 5000ms)
* @param startAtBeginning Should the tailer simply tail or should it process the entire
* file and continue tailing (true) or simply start tailing from the
* end of the file
*/
public LogFileTailer( File file, long sampleInterval, boolean startAtBeginning )
{
this.logfile = file;
this.sampleInterval = sampleInterval;
}
public void addLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.add( l );
}
public void removeLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.remove( l );
}
protected void fireNewLogFileLine( String line )
{
for( Iterator i=this.listeners.iterator(); i.hasNext(); )
{
LogFileTailerListener l = ( LogFileTailerListener )i.next();
l.newLogFileLine( line );
}
}
public void stopTailing()
{
this.tailing = false;
}
public void run()
{
// The file pointer keeps track of where we are in the file
long filePointer = 0;
// Determine start point
if( this.startAtBeginning )
{
filePointer = 0;
}
else
{
filePointer = this.logfile.length();
}
try
{
// Start tailing
this.tailing = true;
RandomAccessFile file = new RandomAccessFile( logfile, "r" );
while( this.tailing )
{
try
{
// Compare the length of the file to the file pointer
long fileLength = this.logfile.length();
if( fileLength < filePointer )
{
// Log file must have been rotated or deleted;
// reopen the file and reset the file pointer
file = new RandomAccessFile( logfile, "r" );
filePointer = 0;
}
if( fileLength > filePointer )
{
// There is data to read
file.seek( filePointer );
String line = file.readLine();
while( line != null )
{
this.fireNewLogFileLine( line );
line = file.readLine();
}
filePointer = file.getFilePointer();
}
// Sleep for the specified interval
sleep( this.sampleInterval );
}
catch( Exception e )
{
}
}
// Close the file that we are tailing
file.close();
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
The LogFileTailer class extends java.lang.Thread. To use it, you must create an instance of it, and call its start() method. This begins the execution of the LogFileTailer's run() method in a new thread. As the LogFileTailer class is constructed, it is passed a file to tail; a sampling interval (defaulted to 5 seconds) to check the file for updates; and a boolean flag specifying whether the tail should parse the entire existing file and then start tailing, or simply start tailing from the end (the default behavior).
The LogFileTailer class maintains a Set of listeners, implementing the LogFileTailerListener interface, that are notified when the log file is notified. Listing 2 shows the code for the LogFileTailerListener interface.
Listing 2. LogFileTailerListener.java
package com.javasrc.tuning.agent.logfile;
/**
* Provides listener notification methods when a tailed log file is updated
*/
public interface LogFileTailerListener
{
/**
* A new line has been added to the tailed log file
*
* @param line The new line that has been added to the tailed log file
*/
public void newLogFileLine( String line );
}
The LogFileTailerListener interface defines a single method, newLogFileLine(), that is called once for each line added to the tailed log file.
The LogFileTailer class's run() method begins by determining whether the file pointer should point to the beginning of the file or the end of the file (from the startAtBeginning boolean parameter passed to the constructor). Next, it opens the log file to be tailed and then sits in a loop checking the file length. The file length can fall into one of three modes:
- The file length is the same as the file pointer value. This denotes that the file has not been updated.
- The file length is greater than the file pointer. This denotes that the file has been updated.
- The file length is less than the file pointer. This denotes that the log file has been deleted or (more likely) rotated, so we need to start from the beginning of the file again.
In the first case, the LogFileTailer does not do anything; it just sleeps for the sample interval.
In the second case, the LogFileTailer calls the RandomAccessFile class's seek() method to position the file pointer appropriately. It then repeatedly calls readLine() until it reaches the end of the file stream. For each line that it reads, the program fires an event notification to its listeners.
If the final case, the program reopens the log file and resets the file pointer back to the beginning of the log file. It then processes the entire file.
As an example of using this framework to tail a text file, I created a Java console-based application that performs functionality similar to the tail –f command in UNIX. Its source code is shown in listing 3.
Listing 3. Tail.java
package com.javasrc.tuning.agent.logfile;
// Import the Java classes
import java.util.*;
import java.io.*;
/**
* Implements console-based log file tailing, or more specifically, tail following:
* it is somewhat equivalent to the unix command "tail -f"
*/
public class Tail implements LogFileTailerListener
{
/**
* The log file tailer
*/
private LogFileTailer tailer;
/**
* Creates a new Tail instance to follow the specified file
*/
public Tail( String filename )
{
tailer = new LogFileTailer( new File( filename ), 1000, false );
tailer.addLogFileTailerListener( this );
tailer.start();
}
/**
* A new line has been added to the tailed log file
*
* @param line The new line that has been added to the tailed log file
*/
public void newLogFileLine(String line)
{
System.out.println( line );
}
/**
* Command-line launcher
*/
public static void main( String[] args )
{
if( args.length < 1 )
{
System.out.println( "Usage: Tail <filename>" );
System.exit( 0 );
}
Tail tail = new Tail( args[ 0 ] );
}
}
Listing 3 demonstrates the simplicity of implementing a tail follower program. The Tailer class simply creates a LogFileTailer instance that follows the specified log file. It registers itself as a LogFileTailerListener and prints out new lines as they appear.
In retrospect, to create a GUI tailer, it would be a simple matter of creating a Swing GUI to redirect these messages to a JTextArea or even to create a set of JInternalFrame instances to tail multiple log files simultaneously. And, to take it a step further, you could build context-sensitive processors. They could color code text based on the type of message being received (e.g. tailing a WebLogic log might color fatal and emergency messages red, warning messages orange, and informational messages blue). Or they build a JTable that interprets and parses log messages for better sorting and filtering. The possibilities are vast. If I get around to building some of this functionality, I will surely share it with you!
Summary
Cool tools: it's a collection of cool tools that I develop and find on the Internet. Thus far, I have shared my LogFileTailer class with you.
If you have any cool tools that you would like to share with everyone, send me an e-mail at lygado@yahoo.com with your code. I will do my best to post an article about it. Sorry, there's no financial compensation; but if you want to contribute your tools to the world or gain some visibility, if it is cool enough then I would love to help you!
