Java Cookbook, Second Edition
Problem
You want anything written to a stream, such as the standard output System.out, or the standard error System.err, to appear there but also be logged into a file. Solution
Subclass PrintStream and have its write( ) methods write to two streams. Then use system.setErr( ) or setOut( ), as in Recipe 10.9, to replace the existing standard stream with this "tee" PrintStream subclass. Discussion
Classes are meant to be subclassed. Here we're just subclassing PrintStream and adding a bit of functionality: a second PrintStream! I wrote a class called TeePrintStream, named after the ancient Unix command tee. That command allowed you to duplicate, or "tee off," a copy of the data being written on a " pipeline" between two programs. The original Unix tee command is used like this: the | character creates a pipeline in which the standard output of one program becomes the standard input to the next. This often-used example of pipes shows how many users are logged into a Unix server: who | wc -l This runs the who program (which lists who is logged into the system, one name per line along with the terminal port and login time) and sends its output, not to the terminal, but rather into the standard input of the word count (wc) program. Here, wc is being asked to count lines, not words; hence the -l option. To tee a copy of the intermediate data into a file, you might say: who | tee wholist | wc -l which creates a file wholist containing the data. For the curious, the file wholist might look something like this: ian ttyC0 Mar 14 09:59 ben ttyC3 Mar 14 10:23 ian ttyp4 Mar 14 13:46 (daroad.darwinsys.com) So both the previous command sequences would print 3 as their output. TeePrintStream is an attempt to capture the spirit of the tee command. It can be used like this: System.setErr(new TeePrintStream(System.err, "err.log")); // ...lots of code that occasionally writes to System.err... Or might. System.setErr( ) is a means of specifying the destination of text printed to System.err (there are also System.setOut( ) and System.setIn( )). This code results in any messages that printed to System.err to print to wherever System.err was previously directed (normally the terminal, but possibly a text window in an IDE) and into the file err.log. This technique is not limited to the three standard streams. A TeePrintStream can be passed to any method that wants a PrintStream. Or, for that matter, an OutputStream. And you can adapt the technique for BufferedInputStreams, PrintWriters, BufferedReaders, and so on. Since TeePrintStream is fairly simple, I'll list the main parts of it here (see the online source for the complete version): import java.io.*; public class TeePrintStream extends PrintStream { protected PrintStream parent; protected String fileName; /* Construct a TeePrintStream given an existing Stream and a filename. */ public TeePrintStream(PrintStream os, String fn) throws IOException { this(os, fn, false); } /* Construct a TeePrintStream given an existing Stream, a filename, * and a boolean to control the flush operation. */ public TeePrintStream(PrintStream orig, String fn, boolean flush) throws IOException { super(new FileOutputStream(fn), flush); fileName = fn; parent = orig; } /** Return true if either stream has an error. */ public boolean checkError( ) { return parent.checkError( ) || super.checkError( ); } /** override write( ). This is the actual "tee" operation! */ public void write(int x) { parent.write(x); // "write once; super.write(x); // write somewhere else" } /** override write( ) */ public void write(byte[] x, int o, int l) { parent.write(x, o, l); super.write(x, o, l); } /** Close both streams. */ public void close( ) { parent.close( ); super.close( ); } } It's worth mentioning that I do not need to override all the polymorphic forms of print( ) and println( ). Since these all ultimately use one of the forms of write( ), if you override the print and println methods to do the tee-ing as well, you can get several additional copies of the data written out. |