FileDocCategorySizeDatePackage
SystemOutandErrHandler.javaAPI DocGlassfish v2 API15181Fri May 04 22:35:44 BST 2007com.sun.enterprise.server.logging

SystemOutandErrHandler.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.server.logging;
import com.sun.enterprise.J2EESecurityManager;

import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.logging.Handler;
import java.util.logging.Filter;
import java.util.logging.ConsoleHandler;
import java.util.logging.ErrorManager;
import java.util.logging.LogManager;

import java.io.*;
import java.util.*;

import com.sun.logging.LogDomains;


/**
 * Class SystemOutandErrHandler extends PrintStream & is for use within the 
 * Application Server. The init is called from PEMain and this creates 2
 * instances of this class - one for Err & other for Out.
 *
 * This is not expected to be performance intensive and hence more defensive
 * programming is done to reduce risk.
 */
public class SystemOutandErrHandler {

	private static final String SYSTEMERR_LOGGER = "javax.enterprise.system.stream.err";
	private static final String SYSTEMOUT_LOGGER = "javax.enterprise.system.stream.out";
	private static PrintStream originalSystemErr; 
	private static Logger soLogger = null; 
	private static Logger seLogger = null; 
	private static Level errLogLevel = Level.WARNING;
	private static Level outLogLevel = Level.INFO;
	private static String lineSeparator;
	private static int lineSeparatorSize;
	private LoggingPrintStream lout, lerr;

	public SystemOutandErrHandler() {
	    if (originalSystemErr != null) {
		RuntimeException e = new RuntimeException("recursivecall");
                originalSystemErr.println("recursive call into SystemOutandErrhandler");
                e.printStackTrace(originalSystemErr);
		return;
	    }

	    LoggingByteArrayOutputStream buf = new LoggingByteArrayOutputStream();
	    // Initialise logger and set new systemout
	    seLogger = java.util.logging.Logger.getLogger(SYSTEMERR_LOGGER);
	    // seLogger = LogDomains.getLogger(LogDomains.SYSTEMERR_LOGGER, false);
	    buf.setLogger(seLogger, errLogLevel);
            originalSystemErr = System.err;
	    lerr = (new LoggingPrintStream(buf));
            lerr.setLogger(seLogger);
	    System.setErr(lerr);

	    buf = new LoggingByteArrayOutputStream();
	    // Initialise logger and set new systemout
	    soLogger = java.util.logging.Logger.getLogger(SYSTEMOUT_LOGGER);
	    buf.setLogger(soLogger, outLogLevel);
	    lout = (new LoggingPrintStream(buf));
            lout.setLogger(soLogger);
	    System.setOut(lout);

	    lineSeparator = (String) java.security.AccessController.doPrivileged(
		new sun.security.action.GetPropertyAction("line.separator"));
	    lineSeparatorSize = lineSeparator.length();

	}

/*
 * LoggingPrintStream creates a PrintStream with a
 * LoggingByteArrayOutputStream as its OutputStream. Once it is
 * set as the System.out or System.err, all outputs to these
 * PrintStreams will end up in LoggingByteArrayOutputStream
 * which will log these on a flush.
 * This simple behavious has a negative side effect that
 * stack traces are logged with each line being a new log record.
 * The reason for above is that printStackTrace converts each line
 * into a separate println, causing a flush at the end of each.
 * One option that was thought of to smooth this over was to see
 * if the caller of println is Throwable.[some set of methods].
 * Unfortunately, there are others who interpose on System.out and err
 * (like jasper) which makes that check untenable.
 * Hence the logic currently used is to see if there is a println(Throwable)
 * and do a printStackTrace and log the complete StackTrace ourselves.
 * If this is followed by a series of printlns, then we keep ignoring
 * those printlns as long as they were the same as that recorded by
 * the stackTrace. This needs to be captured on a per thread basis
 * to take care of potentially parallel operations.
 * Care is taken to optimise the frequently used path where exceptions
 * are not being printed.
 */

	private class LoggingPrintStream extends PrintStream {
	    private ByteArrayOutputStream bufOut;
            private Logger logger=null;
            LogManager logManager=null;
	    private ThreadLocal perThreadStObjects = new ThreadLocal();


	    public LoggingPrintStream(LoggingByteArrayOutputStream buf) {
		// create printStream with autoflush set to true
     		super(buf, true);
		bufOut = buf;
                logManager = LogManager.getLogManager( );
	    }

            public void setLogger(Logger l) {
                logger=l;
            }

	    public void println(Object x) {
                if (!checkLocks())  return;

	    	StackTraceObjects sTO;

		if ( (sTO = (StackTraceObjects) perThreadStObjects.get()) != null ) {
		    /*
		     * should not happen, but being safe.
		     * Only case under which we can come here is if there is
		     * code which does synchronized(System.err) and then does
		     * System.err.println(Throwable) without printing stackTrace
		     * other than java.lang.Throwable. We could have done
		     * this check prior to the check on holdsLock, but since
		     * that is the most common path, let us avoid any overhead
		     * println(String) will also do above check and hence there
		     * is no danger of missing out on valid printlns
		     */
		    perThreadStObjects.set(null);
		}

		if ( !(x instanceof java.lang.Throwable) ) {
		    // No special processing if it is not an exception.
                    super.println(x);
		    return;
		}

		// if we pass all these checks, then we log the stacktrace
		sTO = new StackTraceObjects((Throwable)x);
		perThreadStObjects.set(sTO);
		super.println(sTO.toString());
	    }
	    public void println(String str) {
                if (!checkLocks())  return;

	    	StackTraceObjects sTO;
		sTO = (StackTraceObjects) perThreadStObjects.get();
		if ( sTO == null ) {
		    // lets get done with the common case fast
		    super.println(str); 
		    return;
		}

		if ( !sTO.ignorePrintln(str) ) {
		    perThreadStObjects.set(null);
		    super.println(str);
		    return;
		}

		if (sTO.checkCompletion()) {
		    perThreadStObjects.set(null);
		    return;
		}
	    }

            public void print(String x) {
                if (checkLocks())
                    super.print(x);
            }


            public void print(Object x) {
                if (checkLocks())
                    super.print(x);
            }

            public void print(boolean x) {
                if (checkLocks()) {
	            super.print(x);
                }
            }
            public void println(boolean x) {
                if (checkLocks())
                    super.println(x);
            }

            public void print(char x) {
                if (checkLocks()) {
	           super.print(x);
                }
            }
            public void println(char x) {
                if (checkLocks()) 
                   super.println(x);
            }

            public void print(int x) {
                if (checkLocks()) {
	            super.print(x);
                }
            } 
            public void println(int x) {
                if (checkLocks())
                    super.println(x);
            }

            public void print(long x) {
                if (checkLocks()) {
	            super.print(x);
                }
            } 
            public void println(long x) {
                if (checkLocks())
                    super.println(x);
            }

            public void print(float x) {
                if (checkLocks()) {
	            super.print(x);
                }
            }
            public void println(float x) {
                if (checkLocks())
                    super.println(x);
            }

            public void print(double x) {
                if (checkLocks()) { 
	            super.print(x);
                }
            } 
            public void println(double x) {
                if (checkLocks()) 
                    super.println(x);
            }
 
            public void print(char[] x) {
                if (checkLocks()) {
	            super.print(x);
                }
            }
            public void println(char[] x) {
                if (checkLocks())
                    super.println(x);
            }


            public void println() {
                if (checkLocks()) {
	            super.println();
                }
            }
	    
            public void write(byte[] buf, int off, int len) {
                if (checkLocks()) {
	            super.write(buf,off,len);
                }              
            }

            public void write(int b) {
                if (checkLocks()) {
	            super.write(b);
                }
            }
	    
            /*
              LoggingPrintStream class is to support the java System.err and System.out
              redirection to Appserver log file--server.log.
              When Java IO is redirected and System.out.println(...) is invoked by a thread with
              LogManager or Logger(SYSTEMERR_LOGGER,SYSTEOUT_LOGGER) locked, all kind of dead
              locks among threads will happen.
              These dead locks are easily reproduced when jvm system properties 
              "-Djava.security.manager" and "-Djava.security.debug=access,failure" are defined.
              These dead lcoks are basically because each thread has its own sequence of
              acquiring lock objects(LogManager,Logger,FileandSysLogHandler, the buffer inside
              LoggingPrintStream).
              There is no obvious way to define the lock hierarchy and control the lock sequence;
              Trylock is not a strightforward solution either.Beside they both create heavy
              dependence on the detail implementation of JDK and Appserver.
              
              This method(checkLocks) is to find which locks current thread has and 
              LoggingPrintStream object will decide whether to continue to do printing or 
              give ip up to avoid the dead lock.
             */
            private boolean checkLocks() {
                Thread t = Thread.currentThread();
                if ( !t.holdsLock(logger) && !t.holdsLock(logManager) ) {
                   return true;
                }
                return false;
            }
	}


/*
 * Creates an OutputStream in which we capture the output and log
 * to the logfile on a flush call
 */
	private class  LoggingByteArrayOutputStream extends ByteArrayOutputStream {
	    private Level logLevel;
	    private Logger logger;
	    private boolean recursiveWarnIssued = false;
	    private boolean inFlush = false;

		public LoggingByteArrayOutputStream() {
			super();
		}
		public void flush() throws IOException {
		    if (super.size() == 0) {
			// avoid empty lines on multiple flushes
			return;
		    }
		    if ((super.size() == lineSeparatorSize) &&
			super.toString().equals(lineSeparator)) {
			// avoid empty lineseparator on multiple flushes
			return;
		    }
		    synchronized (this) {
			if (inFlush) {
                	    //originalSystemErr.println(this.toString()); //originalSystemErr IS System.err.
			    if (!recursiveWarnIssued) {
				RuntimeException e = new RuntimeException("recursivecall");
                		originalSystemErr.println("recursive call into SystemOutandErrhandler");
                		e.printStackTrace(originalSystemErr);
				recursiveWarnIssued = true;
			    }
			    return;
			}
			inFlush = true;
			super.flush();
			logger.log(logLevel, this.toString());
			inFlush = false;
			super.reset();
                    }
		}
		public void setLogger(Logger systemlogger, Level loglevel) {
			logger = systemlogger;
			logLevel = loglevel;
		}
	}
	

/*
 * StackTraceObjects keeps track of StackTrace printed
 * by a thread as a result of println(Throwable) and
 * it keeps track of subsequent println(String) to
 * avoid duplicate logging of stacktrace
 */
	private class	StackTraceObjects {

	    private ByteArrayOutputStream stackTraceBuf;
	    private PrintStream stStream;
	    private String stString;
	    private ByteArrayOutputStream comparisonBuf;
	    private int comparisonBufIndex = 0;
	    private PrintStream cbStream;
	    private int stackTraceBufBytes = 0;
	    private int charsIgnored = 0;

	    private	StackTraceObjects(Throwable x) {
		// alloc buffer for getting stack trace.
		stackTraceBuf = new ByteArrayOutputStream();
		stStream = new PrintStream(stackTraceBuf, true);
		comparisonBuf = new ByteArrayOutputStream();
		cbStream = new PrintStream(comparisonBuf, true);
		((Throwable)x).printStackTrace(stStream);
		stString = stackTraceBuf.toString();
		stackTraceBufBytes = stackTraceBuf.size();
		// helps keep our stack trace skipping logic simpler.
		cbStream.println(x);
	    }

	    public String toString() {
		return stString;
	    }

	    boolean ignorePrintln(String str) {
		String cbString;
		int cbLen;
		cbStream.println(str);
		cbString = comparisonBuf.toString();
		cbLen = cbString.length();
		if (stString.regionMatches(charsIgnored, cbString, 0, cbLen)) {
			charsIgnored+= cbLen;
			comparisonBuf.reset();
			return true;
		}

		return false;

	    }

	    boolean checkCompletion() {
		if ( charsIgnored >= stackTraceBufBytes ) {
			return true;
		} else {
			return false;
		}
	    }
	}
}