// CommonLogger.java // $Id: CommonLogger.java,v 1.23 1998/07/01 11:44:43 ylafon Exp $ // (c) COPYRIGHT MIT and INRIA, 1996. // Please first read the full copyright statement in file COPYRIGHT.html package org.w3c.jigsaw.http ; import java.io.* ; import java.util.Date ; import org.w3c.jigsaw.daemon.*; import org.w3c.jigsaw.auth.AuthFilter; import org.w3c.util.*; /** * The CommonLogger class implements the abstract Logger class. * The resulting log will conform to the * common log format). * @see org.w3c.jigsaw.core.Logger */ public class CommonLogger extends Logger implements PropertyMonitoring { private static final String monthnames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /** * Name of the property indicating the log file. * This property indicates the name of the log file to use. *
This property defaults to the log
file in the server
* log directory.
*/
public static final
String LOGNAME_P = "org.w3c.jigsaw.logger.logname" ;
/**
* Name of the property indicating the error log file.
* This property indicates the name of the error log file to use.
*
This property defaults to the errlog
file in the
* server log directory.
*/
public static final
String ERRLOGNAME_P = "org.w3c.jigsaw.logger.errlogname" ;
/**
* Name of the property indicating the server trace file.
* This property indicates the name of the trace file to use.
*
This property defaults to the trace
file in the
* server log directory.
*/
public static final
String TRACELOGNAME_P = "org.w3c.jigsaw.logger.tracelogname";
/**
* Name of the property indicating the buffer size for the logger.
* This buffer size applies only the the log file, not to the error
* log file, or the trace log file. It can be set to zero if you want
* no buffering.
*
This property default to 4096. */ public static final String BUFSIZE_P = "org.w3c.jigsaw.logger.bufferSize"; private byte msgbuf[] = null ; protected RandomAccessFile log = null ; protected RandomAccessFile errlog = null ; protected RandomAccessFile trace = null ; protected httpd server = null ; protected ObservableProperties props = null ; protected String logdir = "logs" ; protected int bufsize = 8192; protected int bufptr = 0; protected byte buffer[] = null; /** * Property monitoring for the logger. * The logger allows you to dynamically (typically through the property * setter) change the names of the file to which it logs error, access * and traces. * @param name The name of the property that has changed. * @return A boolean, true if the change was made, * false otherwise. */ public boolean propertyChanged (String name) { if ( name.equals(LOGNAME_P) ) { try { openLogFile () ; } catch (Exception e) { e.printStackTrace() ; return false ; } return true ; } else if ( name.equals(ERRLOGNAME_P) ) { try { openErrorLogFile() ; } catch (Exception e) { e.printStackTrace() ; return false ; } return true ; } else if ( name.equals(TRACELOGNAME_P) ) { try { openTraceFile() ; } catch (Exception e) { e.printStackTrace() ; return false ; } return true ; } else if ( name.equals(BUFSIZE_P) ) { synchronized (this) { bufsize = props.getInteger(name, bufsize); // Reset buffer before resizing: if ( bufptr > 0 ) { try { log.write(buffer, 0, bufptr); bufptr = 0; } catch (IOException ex) { } } // Set new buffer: buffer = (bufsize > 0) ? new byte[bufsize] : null; return true; } } else { return true ; } } /** * Output the given message to the given RandomAccessFile. * This method makes its best effort to avoid one byte writes (which you * get when writing the string as a whole). It first copies the string * bytes into a private byte array, and than, write them all at once. * @param f The RandomAccessFile to write to, which should be one of * log, errlog or trace. * @param msg The message to be written. * @exception IOException If writing to the output failed. */ protected synchronized void output (RandomAccessFile f, String msg) throws IOException { int len = msg.length() ; if ( len > msgbuf.length ) msgbuf = new byte[len] ; msg.getBytes (0, len, msgbuf, 0) ; f.write (msgbuf, 0, len) ; } protected synchronized void appendLogBuffer(String msg) throws IOException { int msglen = msg.length(); if ( bufptr + msglen > buffer.length ) { // Flush the buffer: log.write(buffer, 0, bufptr); bufptr = 0; // Check for messages greater then buffer: if ( msglen > buffer.length ) { byte huge[] = new byte[msglen]; msg.getBytes(0, msglen, huge, 0); log.write(huge, 0, msglen); return; } } else { // Append that message to buffer: msg.getBytes(0, msglen, buffer, bufptr); bufptr += msglen; } } protected void logmsg (String msg) { if ( log != null ) { try { if ( buffer == null ) { output (log, msg) ; } else { appendLogBuffer(msg); } } catch (IOException e) { throw new HTTPRuntimeException (this,"logmsg",e.getMessage()) ; } } } protected void errlogmsg (String msg) { if ( errlog != null ) { try { output (errlog, msg) ; } catch (IOException e) { throw new HTTPRuntimeException (this , "errlogmsg" , e.getMessage()) ; } } } protected void tracemsg (String msg) { if ( trace != null ) { try { output (trace, msg) ; } catch (IOException e) { throw new HTTPRuntimeException (this , "tracemsg" , e.getMessage()) ; } } } /** * Log the given HTTP transaction. * This is shamelessly slow. */ public void log (Request request, Reply reply, int nbytes, long duration) { Client client = request.getClient() ; String entry = null ; long date = reply.getDate(); Date now = (date < 0) ? new Date() : new Date(date); String user = (String) request.getState(AuthFilter.STATE_AUTHUSER); entry = client.getInetAddress().getHostAddress() + " " + "-" // user name + " " + ((user == null ) ? "-" : user) // auth user name + ((now.getDate() < 10) ? " [0" : " [") + (now.getDate() // current date + "/" + monthnames[now.getMonth()] + "/" + (now.getYear() + 1900) + ((now.getHours() < 10) ? (":0" + now.getHours()) : (":" + now.getHours())) + ((now.getMinutes() < 10) ? (":0" + now.getMinutes()) : (":" + now.getMinutes())) + ((now.getSeconds() < 10) ? (":0" + now.getSeconds()) : (":" + now.getSeconds())) + ((now.getTimezoneOffset() < 0) ? " " + (now.getTimezoneOffset() / 60) : " +" + (now.getTimezoneOffset() / 60)) + "]") + " \"" + request.getMethod() // request line + " " + request.getURL() + " " + request.getVersion() + "\" " + reply.getStatus() // reply status + " " + nbytes // # of emited bytes + "\n" ; logmsg (entry) ; } public void log(String msg) { logmsg(msg); } public void errlog (Client client, String msg) { errlogmsg (client + ": " + msg + "\n") ; } public void errlog (String msg) { errlogmsg (msg + "\n") ; } public void trace (Client client, String msg) { tracemsg (client + ": " + msg + "\n") ; } public void trace (String msg) { tracemsg (msg + "\n") ; } /** * Get the name for the file indicated by the provided property. * This method first looks for a property value. If none is found, it * than constructs a default filename from the server root, by * using the provided default name. *
This method shall either succeed in getting a filename, or throw * a runtime exception. * @param propname The name of the property. * @param def The default file name to use. * @exception HTTPRuntimeException If no file name could be deduced from * the provided set of properties. */ protected String getFilename (String propname, String def) { String filename = props.getString (propname, null) ; if ( filename == null ) { File root_dir = server.getRootDirectory(); if ( root_dir == null ) { String msg = "unable to build a default value for the \"" + propname + "\" value." ; throw new HTTPRuntimeException (this.getClass().getName() , "getFilename" , msg) ; } File flogdir = new File(root_dir, logdir) ; return (new File(flogdir, def)).getAbsolutePath() ; } else { return filename ; } } /** * Open this logger log file. */ protected void openLogFile () { String logname = getFilename(LOGNAME_P, "log") ; try { RandomAccessFile old = log ; log = new RandomAccessFile (logname, "rw") ; log.seek (log.length()) ; if ( old != null ) old.close () ; } catch (IOException e) { throw new HTTPRuntimeException (this.getClass().getName() , "openLogFile" , "unable to open "+logname); } } /** * Open this logger error log file. */ protected void openErrorLogFile () { String errlogname = getFilename (ERRLOGNAME_P, "errlog") ; try { RandomAccessFile old = errlog ; errlog = new RandomAccessFile (errlogname, "rw") ; errlog.seek (errlog.length()) ; if ( old != null ) old.close() ; } catch (IOException e) { throw new HTTPRuntimeException (this.getClass().getName() , "openErrorLogFile" , "unable to open "+errlogname); } } /** * Open this logger trace file. */ protected void openTraceFile () { String tracename = getFilename (TRACELOGNAME_P, "traces"); try { RandomAccessFile old = trace ; trace = new RandomAccessFile (tracename, "rw") ; trace.seek (trace.length()) ; if ( old != null ) old.close() ; } catch (IOException e) { throw new HTTPRuntimeException (this.getClass().getName() , "openTraceFile" , "unable to open "+tracename); } } /** * Save all pending data to stable storage. */ public synchronized void sync() { try { if ((buffer != null) && (bufptr > 0)) { log.write(buffer, 0, bufptr); bufptr = 0; } } catch (IOException ex) { server.errlog(getClass().getName() + ": IO exception in method sync \"" + ex.getMessage() + "\"."); } } /** * Shutdown this logger. */ public synchronized void shutdown () { server.getProperties().unregisterObserver (this) ; try { // Flush any pending output: if ((buffer != null) && (bufptr > 0)) { log.write(buffer, 0, bufptr); bufptr = 0; } log.close() ; log = null ; errlog.close() ; errlog = null ; trace.close() ; trace = null ; } catch (IOException ex) { server.errlog(getClass().getName() + ": IO exception in method shutdown \"" + ex.getMessage() + "\"."); } } /** * Initialize this logger for the given server. * This method gets the server properties describe above to * initialize its various log files. * @param server The server to which thiss logger should initialize. */ public void initialize (httpd server) { this.server = server ; this.props = server.getProperties() ; // Register for property changes: props.registerObserver (this) ; // Open the various logs: openLogFile () ; openErrorLogFile() ; openTraceFile() ; // Setup the log buffer is possible: if ((bufsize = props.getInteger(BUFSIZE_P, bufsize)) > 0 ) buffer = new byte[bufsize]; return ; } /** * Construct a new Logger instance. */ CommonLogger () { this.msgbuf = new byte[128] ; } }