Monday, March 28, 2011

Logging Console Output and Exception

What we are missing

This is a continuation of the previous article. It is suggested to read it first before proceeding.

The SystemOutLogger demo in the previous article has some major drawbacks. It only works for String output. For example :
System.out.println("Hello World !")  // this works !
int number=12;
System.out.println(number);          // this doesn't work !

The reason is that we only overrode the method print(String) in SystemOutLogger.java, but not the method print(int).

To overcome this problem, we may choose to override print(int) as well. However, it is very clumsy since obviously we must also override print(float), print(double), print(long)...etc.

A better way is to override the "lower level" method. The write() method is a good candidate, because it is a good guess that all output should finally call the write() method.
We no longer need to override the print() and println() method once we override the write() method.

  public void write(byte[] buf, int off, int len) {
      try {
          super.write(buf, off, len);       // write to file
          consoleOut.write(buf, off, len);  // write to console
      } catch (Exception ignore) {
      }
  }


It is not yet a complete console logger, because we only log System Output, but not System Error. It is simple to fix, just adding a single line to the constructor will do.

System.setErr(this);  // redirect System Error

Demonstration

/******************************************************************************
* File : FullConsoleLogger.java
* Author : http://java.macteki.com/
* Description :
*   A class to log the console output, it logs everything include :
*   1. Normal String.  e.g.  System.out.println("hello");
*   2. Any object or primitive data type.  e.g.   System.out.println(i);
*   3. All exception output.  e.g.  e.printStackTrace();
* Tested with : JDK 1.6
******************************************************************************/


public class FullConsoleLogger extends java.io.PrintStream
{
  private java.io.PrintStream consoleOut=null;

  // initialize a file output stream, also save the console output stream to a variable
  public FullConsoleLogger() throws Exception
  {
      super(new java.io.FileOutputStream("out.txt"),true);
      consoleOut = System.out;
      System.setOut(this);
      System.setErr(this);
  }

  public void write(byte[] buf, int off, int len) {
      try {
          super.write(buf, off, len);
          consoleOut.write(buf, off, len);
      } catch (Exception ignore) {
      }
  }

 

  // Testing program, which can be placed in another source file if you wish
  public static void main(String[] args) throws Exception
  {
    FullConsoleLogger logger = new FullConsoleLogger();
    System.out.println("Logging output to out.txt");
    System.out.print("Hello, ");   // no line break, 
    System.out.println("World !"); // "Hello World !" would be on the same line
    System.out.println();          // Add an empty line
    for (int i=0;i<10;i++)
      System.out.println(i);    
    System.out.println("Thanks for visiting java.macteki.com");
    throw new RuntimeException("Exception !!!");
  }
}

Advance Usage : SwingConsole

With a little effort, we may derive a class from FullConsoleLogger to display the console output to a Swing GUI component such as JTextArea. Since it is a subclass of FullConsoleLogger, both files must be in the same folder to compile and run.
javac *.java
java SwingConsole
/******************************************************************************
* File : SwingConsole.java
* Author : http://java.macteki.com/
* Description :
*   1. log the console output (out.txt)
*   2. print the console output to a Swing component (JTextArea with scrollbar)
* Tested with : JDK 1.6
******************************************************************************/


public class SwingConsole extends FullConsoleLogger
{
  private javax.swing.JPanel myPanel=null;
  private javax.swing.JTextArea myTextArea=null;
  private StringBuffer myBuffer=new StringBuffer();

  // initialize a file output stream, also save the console output stream to a variable
  public SwingConsole() throws Exception
  {

      // initialize a JPanel for holding the output
      myTextArea = new javax.swing.JTextArea(); 
      myTextArea.setBackground(java.awt.Color.CYAN);
      myTextArea.setForeground(java.awt.Color.BLUE);
      myTextArea.setEditable(false);                  // read only
      // wrap the text area inside a panel with scroll bar.
      javax.swing.JScrollPane scrollingPanel=new javax.swing.JScrollPane(myTextArea);
      scrollingPanel.setPreferredSize(new java.awt.Dimension(580,380));

      // create a panel to hold the scrolling panel
      myPanel = new javax.swing.JPanel();      
      myPanel.add(scrollingPanel);  
      myPanel.setPreferredSize(new java.awt.Dimension(600,400));

  }

  public void write(byte[] buf, int off, int len) {
      try {
          super.write(buf, off, len);
          updateTextArea(buf, off, len);
      } catch (Exception ignore) {
      }
  }


  public void updateTextArea(byte[] buf, int off, int len)
  {
    String s=new String(buf,off,len);
    myBuffer.append(s);  // write to buffer until linefeed found
    if (s.indexOf("\n")>=0) 
    {
      // linefeed found, update TextArea
      myTextArea.append(myBuffer.toString());  // write to JTextArea
      myTextArea.setCaretPosition(myTextArea.getDocument().getLength()); // auto scroll
      keepLines(50);          // keep 50 lines only

      myBuffer.setLength(0);  // clear buffer
    }
  }


  // control how many lines to be kept in the JTextArea to avoid memory error
  public void keepLines(int maxLines)
  {
    try
    {
      javax.swing.text.Element root = myTextArea.getDocument().getDefaultRootElement();
      if (root.getElementCount() > maxLines) {
        javax.swing.text.Element firstLine = root.getElement(0);
        myTextArea.getDocument().remove(0, firstLine.getEndOffset());
      }
    } catch (Exception e)
    {
      e.printStackTrace();
    }
  }


  // return a panel for holding the console output
  public javax.swing.JPanel getPanel()
  {
    return myPanel;
  }

  // Testing program, which can be placed in another source file if you wish
  public static void main(String[] args) throws Exception
  {
    SwingConsole console = new SwingConsole();

    javax.swing.JFrame window=new javax.swing.JFrame();
    window.setTitle("Macteki GUI console");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
    window.add(console.getPanel());
    window.pack();
    window.setVisible(true);

    System.out.println("Logging output to out.txt");
    System.out.print("Hello, ");   // no line break, 
    System.out.println("World !"); // "Hello World !" would be on the same line
    System.out.println();          // Add an empty line
    System.out.println("Thanks for visiting java.macteki.com");

    for (int i=0;i<100;i++)
    {
      System.out.println("progress="+i+"%");
      Thread.sleep(100);
    }

    System.out.println("Completed ! Check the file out.txt.  ");
    System.out.println("You may also scroll up to check the text area");
    System.out.println("If you wish to keep all output in the text area, ");
    System.out.println("set a bigger buffer size in the keepLine() method.");

  }
}


Thanks for reading. Comments are welcome.

No comments:

Post a Comment