costin 01/04/21 11:23:55 Modified: src/share/org/apache/tomcat/modules/config LogSetter.java src/share/org/apache/tomcat/util/qlog LogEntry.java Logger.java QueueLogger.java Added: src/share/org/apache/tomcat/util/qlog LogDaemon.java Log: Work on 1418, plus some code improvements. - the background thread that writes to log files is a separate class, directly controled by the log module, with a clear lifecycle and managing it's own resources. - The LogEntry is now recyclable - A pool is used for LogEntries - small fixes ( the super.sink is used instead of redefining it with a different type) - if the log daemon is stopped, fall back to sync logging ( without losing any log) - less "static", explicit object lifecycle ( static fields are a good hack, but take away flexibility ) - less garbage and string waste Revision Changes Path 1.11 +66 -29 jakarta-tomcat/src/share/org/apache/tomcat/modules/config/LogSetter.java Index: LogSetter.java =================================================================== RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/modules/config/LogSetter.java,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- LogSetter.java 2001/03/09 21:39:32 1.10 +++ LogSetter.java 2001/04/21 18:23:54 1.11 @@ -201,6 +201,13 @@ cm.setNote("tc.LogManager", logManager); Log.setLogManager( logManager ); } + + LogDaemon logDaemon=(LogDaemon)cm.getNote("tc.LogDaemon"); + if( logDaemon==null ) { + logDaemon=new LogDaemon(); + cm.setNote( "tc.LogDaemon", logDaemon ); + logDaemon.start(); + } if( name==null ) { if( servletLogger ) @@ -225,11 +232,69 @@ name=name + "/" + ctx.getId(); } + createLogger(logManager, logDaemon ); + + } + + public void engineInit( ContextManager cm ) + throws TomcatException + { + // make sure it's started + LogDaemon logDaemon=(LogDaemon)cm.getNote("tc.LogDaemon"); + logDaemon.start(); + } + + public void engineShutdown(ContextManager cm) + throws TomcatException + { + if( getContext() != null ) + return; + + cm.getLog().flush(); + // engineShutdown shouldn't be called on local modules anyway ! + + LogDaemon logDaemon=(LogDaemon)cm.getNote("tc.LogDaemon"); + if( logDaemon!=null ) { + try{ + logDaemon.stop(); + } catch( Exception ex ) { + ex.printStackTrace(); + } + // cm.setNote( "tc.LogDaemon", null ); + } + + } + + + + + /** Set default ServletLog for Context if necessary + */ + + public void addContext( ContextManager cm, Context ctx ) + throws TomcatException + { + if( "org/apache/tomcat/facade".equals( name ) && + ctx.getServletLog() == null ) { + ctx.setServletLog( Log.getLog( name, ctx.getId() ) ); + } + } + + /** Adapter and registry for QueueLoggers + */ + static class TomcatLogManager extends LogManager { + + + } + + + private void createLogger(LogManager logManager, LogDaemon logDaemon) { + if( debug>0) log( "Constructing logger " + name + " " + path + " " + ctx ); - // construct a queue logger QueueLogger ql=new QueueLogger(); + ql.setLogDaemon( logDaemon ); if( ! timestamps ) ql.setTimestamp( "false" ); if( tsFormat!=null ) @@ -258,33 +323,5 @@ ctx.setLog( Log.getLog( name, ctx.getId() ) ); } } - } - - /** Set default ServletLog for Context if necessary - */ - - public void addContext( ContextManager cm, Context ctx ) - throws TomcatException - { - if( "org/apache/tomcat/facade".equals( name ) && - ctx.getServletLog() == null ) { - ctx.setServletLog( Log.getLog( name, ctx.getId() ) ); - } - } - - /** Adapter and registry for QueueLoggers - */ - static class TomcatLogManager extends LogManager { - - void addChannel( String name, Log log ) { - - } - - } - - - - // XXX Flush the buffers on shutdown !!!!!! - } 1.2 +22 -11 jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/LogEntry.java Index: LogEntry.java =================================================================== RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/LogEntry.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- LogEntry.java 2001/03/02 04:11:51 1.1 +++ LogEntry.java 2001/04/21 18:23:54 1.2 @@ -72,30 +72,44 @@ public final class LogEntry { String logName; long date=0; + String prefix; String message; Throwable t; QueueLogger l; + + LogEntry(QueueLogger l) { + this.l=l; + } + + QueueLogger getLogger() { + return l; + } - LogEntry(QueueLogger l, long date, String message, Throwable t) { + + void setDate(long date ) { this.date = date; - this.message = message; - this.t = t; - this.l=l; + } + void setPrefix( String prefix ) { + this.prefix=prefix; } - LogEntry( QueueLogger l, String message, Throwable t) { + void setMessage( String message ) { this.message = message; + } + void setThrowable( Throwable t) { this.t = t; - this.l=l; } - + // XXX should move to LogFormat !!! public void print( StringBuffer outSB) { if (date!=0) { l.formatTimestamp( date, outSB ); outSB.append(" - "); } - + if (prefix != null) { + outSB.append(prefix).append( ": "); + } + if (message != null) outSB.append(message); @@ -104,7 +118,4 @@ outSB.append(l.throwableToString( t )); } } - - - } 1.2 +8 -44 jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/Logger.java Index: Logger.java =================================================================== RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/Logger.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Logger.java 2001/03/02 04:11:51 1.1 +++ Logger.java 2001/04/21 18:23:54 1.2 @@ -83,7 +83,8 @@ public abstract class Logger extends LogHandler { // -------------------- Internal fields -------------------- - protected static Writer defaultSink = new OutputStreamWriter(System.err); + protected static PrintWriter defaultSink = + new PrintWriter( new OutputStreamWriter(System.err)); protected long day; @@ -99,14 +100,15 @@ * @param w the default output stream. */ public static void setDefaultSink(Writer w) { - defaultSink = w; + if( w!=null ) + defaultSink = new PrintWriter(w); } // ----- instance (non-static) content ----- protected boolean custom = true; - protected Writer sink = defaultSink; + // protected Writer sink = defaultSink; protected String path; /** @@ -129,44 +131,6 @@ protected DateFormat timestampFormatter = new FastDateFormat(new SimpleDateFormat(timestampFormat)); - /** - * Prints log message and stack trace. - * - * @param message the message to log. - * @param t the exception that was thrown. - * @param verbosityLevel what type of message is this? - * (WARNING/DEBUG/INFO etc) - */ - public final void log(String prefix, String message, Throwable t, - int verbosityLevel) - { - if (prefix != null) { - message = prefix + ": " + message; - } - - if (verbosityLevel <= getVerbosityLevel()) { - // check wheter we are logging to a file - if (path!= null){ - // If the date has changed, switch log files - if (day!=getDay(System.currentTimeMillis())) { - synchronized (this) { - close(); - open(); - } - } - } - realLog(message,t); - } - } - - /** - * Subclasses implement these methods which are called by the - * log(..) methods internally. - * - * @param message the message to log. - * @param t the exception that was thrown. - */ - protected abstract void realLog(String message, Throwable t); /** * Set the path name for the log output file. @@ -200,7 +164,7 @@ file=new File(logName); if (!file.exists()) new File(file.getParent()).mkdirs(); - this.sink = new FileWriter(logName); + this.sink = new PrintWriter( new FileWriter(logName)); } catch (IOException ex) { System.err.print("Unable to open log file: "+path+"! "); System.err.println(" Using stderr as the default."); @@ -327,7 +291,7 @@ static final String START_FORMAT="${"; static final String END_FORMAT="}"; - private String getDatePrefix(long millis,String format) { + protected String getDatePrefix(long millis,String format) { try{ int pos=format.indexOf(Logger.START_FORMAT); int lpos=format.lastIndexOf(Logger.END_FORMAT); @@ -343,7 +307,7 @@ return format; } - private long getDay(long millis){ + protected long getDay(long millis){ return (millis+TimeZone.getDefault().getRawOffset()) / ( 24*60*60*1000 ); } 1.2 +95 -86 jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/QueueLogger.java Index: QueueLogger.java =================================================================== RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/QueueLogger.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- QueueLogger.java 2001/03/02 04:11:51 1.1 +++ QueueLogger.java 2001/04/21 18:23:55 1.2 @@ -62,6 +62,7 @@ import java.util.Date; +import org.apache.tomcat.util.collections.SimplePool; import org.apache.tomcat.util.collections.Queue; /** @@ -73,20 +74,18 @@ * @since Tomcat 3.1 */ public class QueueLogger extends Logger { - /** - * Just one daemon and one queue for all Logger instances.. - */ - static LogDaemon logDaemon = null; - static Queue logQueue = null; + // will be shared by all loggers + private LogDaemon logDaemon = null; + // one pool per QueueLogger + private SimplePool pool=new SimplePool(); public QueueLogger() { - if (logDaemon == null || logQueue == null) { - logQueue = new Queue(); - logDaemon = new LogDaemon(logQueue); - logDaemon.start(); - } } - + + public void setLogDaemon(LogDaemon ld ) { + logDaemon=ld; + } + /** * Adds a log message and stack trace to the queue and returns * immediately. The logger daemon thread will pick it up later and @@ -94,93 +93,103 @@ * * @param message the message to log. * @param t the exception that was thrown. + * @param verbosityLevel what type of message is this? + * (WARNING/DEBUG/INFO etc) */ - final protected void realLog(String message, Throwable t) { - if( timestamp ) - logQueue.put(new LogEntry(this, - System.currentTimeMillis(), - message, t)); - else - logQueue.put(new LogEntry(this, - message, t)); + public void log(String prefix, String message, Throwable t, + int verbosityLevel) + { + // System.out.println("XXXZ " + logDaemon + " " + message ); + if( ! logDaemon.isStarted() ) { + System.out.println("SUPER " + logDaemon + " " + message ); + super.log( prefix, message, t , verbosityLevel ); + return; + } + + if (verbosityLevel <= getVerbosityLevel()) { + // check wheter we are logging to a file + if (path!= null){ + // If the date has changed, switch log files + if (day!=getDay(System.currentTimeMillis())) { + synchronized (this) { + close(); + open(); + } + } + } + + LogEntry entry=(LogEntry)pool.get(); + if( entry == null ) { + entry=new LogEntry(this); + } + + if( timestamp ) { + entry.setDate(System.currentTimeMillis()); + } else { + entry.setDate( 0 ); + } + entry.setPrefix( prefix ); + entry.setMessage( message ); + entry.setThrowable( t ); + logDaemon.add(entry); + } } - /** - * Flush the log. In a separate thread, no wait for the caller. - */ + /** Flush the queue - in a separate thread, so that + caller doesn't have to wait + */ public void flush() { - logDaemon.flush(); + // we need to wait for the log thread to finish, there is + // nothing special we can do ( writing will interfere with the + // log thread, which logs as soon as it gets an entry ) + //emptyQueue(); } - -} - -/** - * The daemon thread that looks in a queue and if it is not empty - * writes out everything in the queue to the sink. - */ -final class LogDaemon extends Thread { - private Queue logQueue; - LogDaemon(Queue logQueue) { - this.logQueue = logQueue; - setDaemon(true); - } - - private static final char[] NEWLINE=Logger.NEWLINE; + // Thread workerThread = new Thread(flusher); + // workerThread.start(); + // Runnable flusher = new Runnable() { + // public void run() { + // QueueLogger.emptyQueue(); + // }}; + private static final char[] NEWLINE=Logger.NEWLINE; // There is only one thread, so we can reuse this char outBuffer[]=new char[512]; // resize - - // NEVER call toString() on StringBuffer!!!!! StringBuffer outSB = new StringBuffer(); - - - private void emptyQueue() { - do { - LogEntry logEntry = - (LogEntry) logQueue.pull(); - QueueLogger tl=logEntry.l; - Writer writer=tl.sink; - if (writer != null) { - try { - outSB.setLength(0); - - logEntry.print( outSB ); - outSB.append( NEWLINE ); - - int len=outSB.length(); - if( len > outBuffer.length ) { - outBuffer=new char[len]; - } - outSB.getChars(0, len, outBuffer, 0); - - writer.write( outBuffer, 0, len ); - writer.flush(); - } catch (Exception ex) { // IOException - ex.printStackTrace(); // nowhere else to write it - } - } - } while (!LogDaemon.this.logQueue.isEmpty()); - } - public void run() { - while (true) { - emptyQueue(); + /** Produce output for a log entry, and then recycle it. + This method is called from a single thread ( the log daemon ) + */ + void log(LogEntry logEntry) { + if( logEntry==null ) { + System.out.println("Null log entry "); + return; } - } - - /** Flush the queue - in a separate thread, so that - caller doesn't have to wait - */ - public void flush() { - Thread workerThread = new Thread(flusher); - workerThread.start(); - } - - Runnable flusher = new Runnable() { - public void run() { - emptyQueue(); + try { + outSB.setLength(0); + + logEntry.print( outSB ); + outSB.append( NEWLINE ); + + int len=outSB.length(); + if( len > outBuffer.length ) { + outBuffer=new char[len]; } - }; + outSB.getChars(0, len, outBuffer, 0); + if (sink != null) { + sink.write( outBuffer, 0, len ); + sink.flush(); + //System.out.print(sink + " " + new String(outBuffer,0,len) ); + } else { + System.out.println("No writer "); + System.out.print(new String(outBuffer,0,len) ); + } + } catch (Exception ex) { // IOException + ex.printStackTrace(); // nowhere else to write it + } + pool.put( logEntry ); + } + + } 1.1 jakarta-tomcat/src/share/org/apache/tomcat/util/qlog/LogDaemon.java Index: LogDaemon.java =================================================================== /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package org.apache.tomcat.util.qlog; import java.io.Writer; import java.io.StringWriter; import java.io.PrintWriter; import java.util.Date; import org.apache.tomcat.util.collections.SimplePool; import org.apache.tomcat.util.collections.Queue; /** * The daemon thread that looks in a queue and if it is not empty * writes out everything in the queue to the sink. */ public final class LogDaemon implements Runnable { private boolean shouldStop=false; private Thread logDaemonThread = null; private Queue logQueue = null; public LogDaemon() { } public void start() { logQueue = new Queue(); logDaemonThread=new Thread(this); logDaemonThread.setName("QueueLogDaemon"); // Don't set it as daemon - we don't want tomcat to exit // logDaemonThread.setDaemon(true); shouldStop=false; logDaemonThread.start(); } public void stop() { if( shouldStop ) return; shouldStop=true; // wait for it to finish logQueue.stop(); // unblock logDaemonThread=null; logQueue=null; } public boolean isStarted() { return logDaemonThread!=null; } public void add(LogEntry logE ) { if( logQueue!=null ) { logQueue.put( logE ); } else { // We're not started, do it synchronously logE.getLogger().log( logE ); } } public void run() { while (true) { // Will block ! LogEntry logEntry = (LogEntry)logQueue.pull(); if( shouldStop ) return; logEntry.getLogger().log( logEntry ); if( shouldStop ) return; } } }