As Remy pointed out earlier today, we tend to trust the people that we vote
in as committers here.  There is no need to pre-post a commit:  We allow
people to scratch their itches here, and nobody is afraid to -1 a really bad
commit.  Just do it.

Personally, I'd make the fileDateFormatter configurable (it is, admittedly
low, on my to-do list to make this configurable for Catalina loggers -- It's
a 3.3 feature that I miss :).

----- Original Message -----
From: "Tim Funk" <[EMAIL PROTECTED]>
To: "Tomcat Developers List" <[EMAIL PROTECTED]>
Sent: Saturday, May 31, 2003 5:22 PM
Subject: [5] Extended AccessLog Valve


> For one of my projects, I am working on an extended access log valve as
> defined by http://www.w3.org/TR/WD-logfile.html. When (if) I finish this,
> does anyone else have interst in this?
>
> It will have the following config options:
> directory - Where to put the logs (like AccessLogValve)
> pattern   - The log pattern
> prefix    - File Name prefix (like AccessLogValve)
> rotatable - Is this a rotating log (like AccessLogValve)
> suffix    - Ending text (like AccessLogValve)
> condition  - Conditionally log requests. This is a String value. If set,
>              then the value in condition will be the key in a lookup
>              for a ServletRequest attribute. If the result is non-null
>              then the logging is skipped for this entry. This can be an
>              easy foundation to allow users to omit monitoring software
>              from the access logs.
> checkExists - If set, if will check for the log files existence on the
>                event an external tool moved it. If the file doesn't exist
>                the close takes place and attempt to reopen the log file
>                occurs.
>
> For app-specific logging, I made this (so far):
> x-A(...) - Pull ... from the servletConext.getAttribute()
> x-C(...) - Pull cookie named ...
> x-R(...) - Pull ... from the servletRequest.getAttribute()
> x-S(...) - Pull ... from the session.getAttribute()
> x-H(...) - Call the appriopriate get or is method in the
>             HttpServletRequest for any of the following based on ...
>     authType
>     characterEncoding
>     contentLength
>     locale
>     protocol
>     remoteUser
>     requestedSessionId
>     requestedSessionIdFromCookie
>     requestedSessionIdValid
>     scheme
>     secure
>
>
> I also expose a JMX operation called rotate. This will allow an external
> "thing" be able move the log file to a new name, then reopen the old
filename.
>
> With any luck, the code is attached (2 attachments). It builds (and seems
to
> run) fine off of tomcat 5. Comments? Would this be OK of I added this to
5?
> If so, are there changes (style/...) which need made first?
>
>
>
>
> -Tim
>
>
>


----------------------------------------------------------------------------
----


> /*
>  * $Header:  Exp $
>  * $Revision:   $
>  * $Date:   $
>  * ====================================================================
>  *
>  * The Apache Software License, Version 1.1
>  *
>  * Copyright (c) 1999-2001 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/>.
>  *
>  * [Additional notices, if required by prior licensing conditions]
>  *
>  */
>
>
> package org.apache.catalina.valves;
>
>
> import java.io.File;
> import java.io.FileWriter;
> import java.io.IOException;
> import java.io.PrintWriter;
> import java.net.InetAddress;
> import java.text.SimpleDateFormat;
> import java.util.Date;
> import java.util.Iterator;
> import java.util.LinkedList;
> import java.util.TimeZone;
> import javax.servlet.ServletException;
> import javax.servlet.ServletRequest;
> import javax.servlet.ServletResponse;
> import javax.servlet.http.Cookie;
> import javax.servlet.http.HttpServletRequest;
> import javax.servlet.http.HttpServletResponse;
> import javax.servlet.http.HttpSession;
> import org.apache.catalina.HttpResponse;
> import org.apache.catalina.Lifecycle;
> import org.apache.catalina.LifecycleEvent;
> import org.apache.catalina.LifecycleException;
> import org.apache.catalina.LifecycleListener;
> import org.apache.catalina.Request;
> import org.apache.catalina.Response;
> import org.apache.catalina.ValveContext;
> import org.apache.catalina.util.LifecycleSupport;
> import org.apache.catalina.util.ServerInfo;
> import org.apache.catalina.util.StringManager;
>
> import org.apache.commons.logging.LogFactory;
> import org.apache.commons.logging.Log;
>
>
>
> /**
>  * An implementation of the W3c Extended Log File Format. See
>  * http://www.w3.org/TR/WD-logfile.html for more information about the
format.
>  *
>  * The following fields are supported:
>  * <ul>
>  * <li><code>c-dns</code>:  Client hostname</li>
>  * <li><code>c-ip</code>:  Client ip address</li>
>  * <li><code>bytes</code>:  bytes served</li>
>  * <li><code>cs-method</code>:  request method</li>
>  * <li><code>cs-uri</code>:  The full uri requested</li>
>  * <li><code>cs-uri-query</code>:  The query string</li>
>  * <li><code>cs-uri-stem</code>:  The uri without query string</li>
>  * <li><code>date</code>:  The date in yyyy-mm-dd  format for GMT</li>
>  * <li><code>s-dns</code>: The server dns entry </li>
>  * <li><code>s-ip</code>:  The server ip address</li>
>  * <li><code>cs(XXX)</code>:  The value of header XXX from client to
server</li>
>  * <li><code>sc(XXX)</code>: The value of header XXX from server to client
</li>
>  * <li><code>sc-status</code>:  The status code</li>
>  * <li><code>time</code>:  Time the request was served</li>
>  * <li><code>time-taken</code>:  Time to server the request</li>
>  * <li><code>x-A(XXX)</code>: Pull XXX attribute from the servlet context
</li>
>  * <li><code>x-C(XXX)</code>: Pull the first cookie of the name XXX </li>
>  * <li><code>x-R(XXX)</code>: Pull XXX attribute from the servlet request
</li>
>  * <li><code>x-S(XXX)</code>: Pull XXX attribute from the session </li>
>  * <li>For any of the x-H(...) the following method will be called from
the
>  *                HttpServletRequestObject </li>
>  * <li><code>x-H(authType)</code>: getAuthType </li>
>  * <li><code>x-H(characterEncoding)</code>: getCharacterEncoding </li>
>  * <li><code>x-H(contentLength)</code>: getContentLength </li>
>  * <li><code>x-H(locale)</code>:  getLocale</li>
>  * <li><code>x-H(protocol)</code>: getProtocol </li>
>  * <li><code>x-H(remoteUser)</code>:  getRemoteUser</li>
>  * <li><code>x-H(requestedSessionId)</code>: getGequestedSessionId</li>
>  * <li><code>x-H(requestedSessionIdFromCookie)</code>:
>  *                  isRequestedSessionIdFromCookie </li>
>  * <li><code>x-H(requestedSessionIdValid)</code>:
>  *                  isRequestedSessionIdValid</li>
>  * <li><code>x-H(scheme)</code>:  getScheme</li>
>  * <li><code>x-H(secure)</code>:  isSecure</li>
>  * </ul>
>  *
>  *
>  *
>  * <p>
>  * Log rotation can be on or off. This is dictated by the rotatable
>  * property.
>  * </p>
>  *
>  * <p>
>  * For UNIX users, another field called <code>checkExists</code>is also
>  * available. If set to true, the log file's existence will be checked
before
>  * each logging. This way an external log rotator can move the file
>  * somewhere and tomcat will start with a new file.
>  * </p>
>  *
>  * <p>
>  * For JMX junkies, a public method called </code>rotate</code> has
>  * been made available to allow you to tell this instance to move
>  * the existing log file to somewhere else start writing a new log file.
>  * </p>
>  *
>  * <p>
>  * Conditional logging is also supported. This can be done with the
>  * <code>condition</code> property.
>  * If the value returned from ServletRequest.getAttribute(condition)
>  * yields a non-null value. The logging will be skipped.
>  * </p>
>  *
>  *
>  * @author Tim Funk
>  * @version $Revision:  $ $Date:  $
>  */
>
> public final class ExtendedAccessLogValve
>     extends ValveBase
>     implements Lifecycle {
>
>
>     // -----------------------------------------------------------
Constructors
>
>
>     /**
>      * Construct a new instance of this class with default property
values.
>      */
>     public ExtendedAccessLogValve() {
>
>         super();
>
>
>     }
>
>
>     // ----------------------------------------------------- Instance
Variables
>     private static Log log =
LogFactory.getLog(ExtendedAccessLogValve.class);
>
>
>     /**
>      * The descriptive information about this implementation.
>      */
>     protected static final String info =
>         "org.apache.catalina.valves.ExtendedAccessLogValve/1.0";
>
>
>     /**
>      * The lifecycle event support for this component.
>      */
>     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
>
>
>
>     /**
>      * The string manager for this package.
>      */
>     private StringManager sm =
>         StringManager.getManager(Constants.Package);
>
>
>     /**
>      * Has this component been started yet?
>      */
>     private boolean started = false;
>
>
>     /**
>      * The as-of date for the currently open log file, or a zero-length
>      * string if there is no open log file.
>      */
>     private String dateStamp = "";
>
>
>     /**
>      * The PrintWriter to which we are currently logging, if any.
>      */
>     private PrintWriter writer = null;
>
>
>     /**
>      * The formatter for the date contained in the file name.
>      */
>     private SimpleDateFormat fileDateFormatter = null;
>
>
>     /**
>      * A date formatter to format a Date into a date in the format
>      * "yyyy-MM-dd".
>      */
>     private SimpleDateFormat dateFormatter = null;
>
>
>     /**
>      * A date formatter to format a Date into a time in the format
>      * "kk:mm:ss" (kk is a 24-hour representation of the hour).
>      */
>     private SimpleDateFormat timeFormatter = null;
>
>
>     /**
>      * My ip address. Look it up once and remember it. Dump this if we can
>      * determine another reliable way to get server ip address since this
>      * server may have many ip's.
>      */
>     private String myIpAddress = null;
>
>
>     /**
>      * My dns name. Look it up once and remember it. Dump this if we can
>      * determine another reliable way to get server name address since
this
>      * server may have many ip's.
>      */
>     private String myDNSName = null;
>
>
>     /**
>      * Holder for all of the fields to log after the pattern is decoded.
>      */
>     private FieldInfo[] fieldInfos;
>
>
>     /**
>      * The current log file we are writing to. Helpful when checkExists
>      * is true.
>      */
>     private File currentLogFile = null;
>
>
>
>     /**
>      * The system time when we last updated the Date that this valve
>      * uses for log lines.
>      */
>     private Date currentDate = null;
>
>
>     /**
>      * Instant when the log daily rotation was last checked.
>      */
>     private long rotationLastChecked = 0L;
>
>
>     /**
>      * The directory in which log files are created.
>      */
>     private String directory = "logs";
>
>
>     /**
>      * The pattern used to format our access log lines.
>      */
>     private String pattern = null;
>
>
>     /**
>      * The prefix that is added to log file filenames.
>      */
>     private String prefix = "access_log.";
>
>
>     /**
>      * Should we rotate our log file? Default is true (like old behavior)
>      */
>     private boolean rotatable = true;
>
>
>     /**
>      * The suffix that is added to log file filenames.
>      */
>     private String suffix = "";
>
>
>     /**
>      * Are we doing conditional logging. default false.
>      */
>     private String condition = null;
>
>
>     /**
>      * Do we check for log file existence? Helpful if an external
>      * agent renames the log file so we can automagically recreate it.
>      */
>     private boolean checkExists = false;
>
>
>
>
>     // -------------------------------------------------------------
Properties
>
>
>     /**
>      * Return the directory in which we create log files.
>      */
>     public String getDirectory() {
>
>         return (directory);
>
>     }
>
>
>     /**
>      * Set the directory in which we create log files.
>      *
>      * @param directory The new log file directory
>      */
>     public void setDirectory(String directory) {
>
>         this.directory = directory;
>
>     }
>
>
>     /**
>      * Return descriptive information about this implementation.
>      */
>     public String getInfo() {
>
>         return (this.info);
>
>     }
>
>
>     /**
>      * Return the format pattern.
>      */
>     public String getPattern() {
>
>         return (this.pattern);
>
>     }
>
>
>     /**
>      * Set the format pattern, first translating any recognized alias.
>      *
>      * @param pattern The new pattern pattern
>      */
>     public void setPattern(String pattern) {
>
>         FieldInfo[] f= decodePattern(pattern);
>         if (f!=null) {
>             this.pattern = pattern;
>             this.fieldInfos = f;
>         }
>     }
>
>
>     /**
>      * Return the log file prefix.
>      */
>     public String getPrefix() {
>
>         return (prefix);
>
>     }
>
>
>     /**
>      * Set the log file prefix.
>      *
>      * @param prefix The new log file prefix
>      */
>     public void setPrefix(String prefix) {
>
>         this.prefix = prefix;
>
>     }
>
>
>     /**
>      * Return true if logs are automatically rotated.
>      */
>     public boolean isRotatable() {
>
>         return rotatable;
>
>     }
>
>
>     /**
>      * Set the value is we should we rotate the logs
>      *
>      * @param rotatable true is we should rotate.
>      */
>     public void setRotatable(boolean rotatable) {
>
>         this.rotatable = rotatable;
>
>     }
>
>
>     /**
>      * Return the log file suffix.
>      */
>     public String getSuffix() {
>
>         return (suffix);
>
>     }
>
>
>     /**
>      * Set the log file suffix.
>      *
>      * @param suffix The new log file suffix
>      */
>     public void setSuffix(String suffix) {
>
>         this.suffix = suffix;
>
>     }
>
>
>
>     /**
>      * Return whether the attribute name to look for when
>      * performing conditional loggging. If null, every
>      * request is logged.
>      */
>     public String getCondition() {
>
>         return condition;
>
>     }
>
>
>     /**
>      * Set the ServletRequest.attribute to look for to perform
>      * conditional logging. Set to null to log everything.
>      *
>      * @param condition Set to null to log everything
>      */
>     public void setCondition(String condition) {
>
>         this.condition = condition;
>
>     }
>
>
>
>     /**
>      * Check for file existence before logging.
>      */
>     public boolean isCheckExists() {
>
>         return checkExists;
>
>     }
>
>
>     /**
>      * Set whether to check for log file existence before logging.
>      *
>      * @param checkExists true meaning to check for file existence.
>      */
>     public void setCheckExists(boolean checkExists) {
>
>         this.checkExists = checkExists;
>
>     }
>
>     // --------------------------------------------------------- Public
Methods
>
>
>     /**
>      * Log a message summarizing the specified request and response,
according
>      * to the format specified by the <code>pattern</code> property.
>      *
>      * @param request Request being processed
>      * @param response Response being processed
>      * @param context The valve context used to invoke the next valve
>      *  in the current processing pipeline
>      *
>      * @exception IOException if an input/output error has occurred
>      * @exception ServletException if a servlet error has occurred
>      */
>     public void invoke(Request request, Response response,
>                        ValveContext context)
>         throws IOException, ServletException {
>
>         // Pass this request on to the next valve in our pipeline
>         long endTime;
>         long runTime;
>         long startTime=System.currentTimeMillis();
>
>         context.invokeNext(request, response);
>
>         endTime = System.currentTimeMillis();
>         runTime = endTime-startTime;
>
>         if (fieldInfos==null || condition!=null &&
>               null!=request.getRequest().getAttribute(condition)) {
>             return;
>         }
>
>
>         Date date = getDate(endTime);
>         StringBuffer result = new StringBuffer();
>
>         for (int i=0; fieldInfos!=null && i<fieldInfos.length; i++) {
>             switch(fieldInfos[i].type) {
>                 case FieldInfo.DATA_CLIENT:
>                     if (FieldInfo.FIELD_IP==fieldInfos[i].location)
>
result.append(request.getRequest().getRemoteAddr());
>                     else if (FieldInfo.FIELD_DNS==fieldInfos[i].location)
>
result.append(request.getRequest().getRemoteHost());
>                     else
>                         result.append("?WTF?"); /* This should never
happen! */
>                     break;
>                 case FieldInfo.DATA_SERVER:
>                     if (FieldInfo.FIELD_IP==fieldInfos[i].location)
>                         result.append(myIpAddress);
>                     else if (FieldInfo.FIELD_DNS==fieldInfos[i].location)
>                         result.append(myDNSName);
>                     else
>                         result.append("?WTF?"); /* This should never
happen! */
>                     break;
>                 case FieldInfo.DATA_REMOTE:
>                     result.append('?'); /* I don't know how to handle
these! */
>                     break;
>                 case FieldInfo.DATA_CLIENT_TO_SERVER:
>                     result.append(getClientToServer(fieldInfos[i],
request));
>                     break;
>                 case FieldInfo.DATA_SERVER_TO_CLIENT:
>                     result.append(getServerToClient(fieldInfos[i],
response));
>                     break;
>                 case FieldInfo.DATA_SERVER_TO_RSERVER:
>                     result.append('-');
>                     break;
>                 case FieldInfo.DATA_RSERVER_TO_SERVER:
>                     result.append('-');
>                     break;
>                 case FieldInfo.DATA_APP_SPECIFIC:
>                     result.append(getAppSpecific(fieldInfos[i], request));
>                     break;
>                 case FieldInfo.DATA_SPECIAL:
>                     if (FieldInfo.SPECIAL_DATE==fieldInfos[i].location)
>                         result.append(dateFormatter.format(date));
>                     else if
(FieldInfo.SPECIAL_TIME_TAKEN==fieldInfos[i].location)
>                         result.append(runTime/1000d);
>                     else if
(FieldInfo.SPECIAL_TIME==fieldInfos[i].location)
>                         result.append(timeFormatter.format(date));
>                     else if
(FieldInfo.SPECIAL_BYTES==fieldInfos[i].location)
>                         result.append(dateFormatter.format(date));
>                     else if
(FieldInfo.SPECIAL_CACHED==fieldInfos[i].location)
>                         result.append('-'); /* I don't know how to
evaluate this! */
>                     else
>                         result.append("?WTF?"); /* This should never
happen! */
>                     break;
>                 default:
>                     result.append("?WTF?"); /* This should never happen!
*/
>             }
>
>             if (fieldInfos[i].postWhiteSpace!=null) {
>                 result.append(fieldInfos[i].postWhiteSpace);
>             }
>         }
>         log(result.toString(), date);
>
>     }
>
>
>     /**
>      * Rename the existing log file to something else. Then open the
>      * old log file name up once again. Intended to be called by a JMX
>      * agent.
>      *
>      *
>      * @param newFileName The file name to move the log file entry to
>      * @return true if a file was rotated with no error
>      */
>     public synchronized boolean rotate(String newFileName) {
>         if (currentLogFile!=null) {
>             File holder = currentLogFile;
>             close();
>             try {
>                 holder.renameTo(new File(newFileName));
>             } catch(Throwable e){
>                 log.error("rotate failed", e);
>             }
>
>             /* Make sure date is correct */
>             currentDate = new Date();
>             fileDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
>             dateStamp = dateFormatter.format(currentDate);
>
>             open();
>             return true;
>         } else {
>             return false;
>         }
>     }
>
>     // -------------------------------------------------------- Private
Methods
>
>
>     /**
>      *  Return the client to server data.
>      *  @param fieldInfo The field to decode.
>      *  @param request The object we pull data from.
>      *  @return The appropriate value.
>      */
>      private String getClientToServer(FieldInfo fieldInfo, Request
request) {
>
>         ServletRequest sr = request.getRequest();
>         HttpServletRequest hsr = null;
>         if (sr instanceof HttpServletRequest)
>             hsr = (HttpServletRequest)sr;
>
>         switch(fieldInfo.location) {
>             case FieldInfo.FIELD_METHOD:
>                 return hsr.getMethod();
>             case FieldInfo.FIELD_URI:
>                 if (null==hsr.getQueryString())
>                     return hsr.getRequestURI();
>                 else
>                     return hsr.getRequestURI() + "?" +
hsr.getQueryString();
>             case FieldInfo.FIELD_URI_STEM:
>                 return hsr.getRequestURI();
>             case FieldInfo.FIELD_URI_QUERY:
>                 if (null==hsr.getQueryString())
>                     return "-";
>                 return hsr.getQueryString();
>             case FieldInfo.FIELD_HEADER:
>                 return wrap(hsr.getHeader(fieldInfo.value));
>             default:
>                 ;
>         }
>         return "-";
>     }
>
>
>     /**
>      *  Return the server to client data.
>      *  @param fieldInfo The field to decode.
>      *  @param response The object we pull data from.
>      *  @return The appropriate value.
>      */
>     private String getServerToClient(FieldInfo fieldInfo, Response
response) {
>         HttpResponse r = (HttpResponse)response;
>         switch(fieldInfo.location) {
>             case FieldInfo.FIELD_STATUS:
>                 return "" + r.getStatus();
>             case FieldInfo.FIELD_COMMENT:
>                 return "?"; /* Not coded yet*/
>             case FieldInfo.FIELD_HEADER:
>                 return wrap(r.getHeader(fieldInfo.value));
>             default:
>                 ;
>         }
>
>         return "-";
>     }
>
>     /**
>      * Get app specific data.
>      * @param fieldInfo The field to decode
>      * @param request Where we will pull the data from.
>      * @return The appropriate value
>      */
>     private String getAppSpecific(FieldInfo fieldInfo, Request request) {
>
>         ServletRequest sr = request.getRequest();
>         HttpServletRequest hsr = null;
>         if (sr instanceof HttpServletRequest)
>             hsr = (HttpServletRequest)sr;
>
>         switch(fieldInfo.xType) {
>             case FieldInfo.X_REQUEST:
>                 return wrap(sr.getAttribute(fieldInfo.value));
>             case FieldInfo.X_SESSION:
>                 HttpSession session = null;
>                 if (hsr!=null){
>                     session = hsr.getSession(false);
>                     if (session!=null)
>                         return
wrap(session.getAttribute(fieldInfo.value));
>                 }
>                 break;
>             case FieldInfo.X_COOKIE:
>                 Cookie[] c = hsr.getCookies();
>                 for (int i=0; c != null && i < c.length; i++){
>                     if (fieldInfo.value.equals(c[i].getName())){
>                         return wrap(c[i].getValue());
>                     }
>                  }
>             case FieldInfo.X_APP:
>                 return wrap(request.getContext().getServletContext()
>                                 .getAttribute(fieldInfo.value));
>             case FieldInfo.X_SERVLET_REQUEST:
>                 if (fieldInfo.location==FieldInfo.X_LOC_AUTHTYPE) {
>                     return wrap(hsr.getAuthType());
>                 } else if (fieldInfo.location==FieldInfo.X_LOC_REMOTEUSER)
{
>                     return wrap(hsr.getRemoteUser());
>                 } else if (fieldInfo.location==
>                             FieldInfo.X_LOC_REQUESTEDSESSIONID) {
>                     return wrap(hsr.getRequestedSessionId());
>                 } else if (fieldInfo.location==
>                             FieldInfo.X_LOC_REQUESTEDSESSIONIDFROMCOOKIE)
{
>                     return wrap(""+hsr.isRequestedSessionIdFromCookie());
>                 } else if (fieldInfo.location==
>                             FieldInfo.X_LOC_REQUESTEDSESSIONIDVALID) {
>                     return wrap(""+hsr.isRequestedSessionIdValid());
>                 } else if
(fieldInfo.location==FieldInfo.X_LOC_CONTENTLENGTH) {
>                     return wrap(""+hsr.getContentLength());
>                 } else if (fieldInfo.location==
>                             FieldInfo.X_LOC_CHARACTERENCODING) {
>                     return wrap(hsr.getCharacterEncoding());
>                 } else if (fieldInfo.location==FieldInfo.X_LOC_LOCALE) {
>                     return wrap(hsr.getLocale());
>                 } else if (fieldInfo.location==FieldInfo.X_LOC_PROTOCOL) {
>                     return wrap(hsr.getProtocol());
>                 } else if (fieldInfo.location==FieldInfo.X_LOC_SCHEME) {
>                     return wrap(hsr.getScheme());
>                 } else if (fieldInfo.location==FieldInfo.X_LOC_SECURE) {
>                     return wrap(""+hsr.isSecure());
>                 }
>                 break;
>             default:
>                 ;
>         }
>
>         return "-";
>     }
>
>
>     private String wrap(Object value) {
>         String svalue;
>         // Does the value contain a " ? If so must encode it
>         if (value==null || "-".equals(value))
>             return "-";
>
>
>         try {
>             svalue = value.toString();
>             if ("".equals(svalue))
>                 return "-";
>         } catch(Throwable e){
>             /* Log error */
>             return "-";
>         }
>
>         /* Wrap all quotes in double quotes. */
>         StringBuffer buffer = new StringBuffer(svalue.length()+2);
>         buffer.append('"');
>         int i=0;
>         while (i<svalue.length()) {
>             int j = svalue.indexOf('"', i);
>             if (j==-1) {
>                 buffer.append(svalue.substring(i));
>                 i=svalue.length();
>             } else {
>                 buffer.append(svalue.substring(i, j+1));
>                 buffer.append('"');
>                 i=j+2;
>             }
>         }
>
>         buffer.append('"');
>         return buffer.toString();
>     }
>
>
>     /**
>      * Close the currently open log file (if any)
>      */
>     private synchronized void close() {
>
>         if (writer == null)
>             return;
>         writer.flush();
>         writer.close();
>         writer = null;
>         currentLogFile = null;
>
>     }
>
>
>     /**
>      * Log the specified message to the log file, switching files if the
date
>      * has changed since the previous log call.
>      *
>      * @param message Message to be logged
>      * @param date the current Date object (so this method doesn't need to
>      *        create a new one)
>      */
>     private void log(String message, Date date) {
>
>         if (rotatable){
>             // Only do a logfile switch check once a second, max.
>             long systime = System.currentTimeMillis();
>             if ((systime - rotationLastChecked) > 1000) {
>
>                 // We need a new currentDate
>                 currentDate = new Date(systime);
>                 rotationLastChecked = systime;
>
>                 // Check for a change of date
>                 String tsDate = fileDateFormatter.format(currentDate);
>
>                 // If the date has changed, switch log files
>                 if (!dateStamp.equals(tsDate)) {
>                     synchronized (this) {
>                         if (!dateStamp.equals(tsDate)) {
>                             close();
>                             dateStamp = tsDate;
>                             open();
>                         }
>                     }
>                 }
>
>             }
>         }
>
>         /* In case something external rotated the file instead */
>         if (checkExists){
>             synchronized (this) {
>                 if (currentLogFile!=null && !currentLogFile.exists()) {
>                     try {
>                         close();
>                     } catch (Throwable e){
>                         log.info("at least this wasn't swallowed", e);
>                     }
>
>                     /* Make sure date is correct */
>                     currentDate = new Date(System.currentTimeMillis());
>                     fileDateFormatter = new
SimpleDateFormat("yyyy-MM-dd");
>                     dateStamp = dateFormatter.format(currentDate);
>
>                     open();
>                 }
>             }
>         }
>
>         // Log this message
>         if (writer != null) {
>             writer.println(message);
>         }
>
>     }
>
>
>     /**
>      * Open the new log file for the date specified by
<code>dateStamp</code>.
>      */
>     private synchronized void open() {
>
>         // Create the directory if necessary
>         File dir = new File(directory);
>         if (!dir.isAbsolute())
>             dir = new File(System.getProperty("catalina.base"),
directory);
>         dir.mkdirs();
>
>         // Open the current log file
>         try {
>             String pathname;
>
>             // If no rotate - no need for dateStamp in fileName
>             if (rotatable){
>                 pathname = dir.getAbsolutePath() + File.separator +
>                             prefix + dateStamp + suffix;
>             } else {
>                 pathname = dir.getAbsolutePath() + File.separator +
>                             prefix + suffix;
>             }
>
>             currentLogFile = new File(pathname);
>             writer = new PrintWriter(new FileWriter(pathname, true),
true);
>             if (currentLogFile.length()==0) {
>                 writer.println("#Fields: " + pattern);
>                 writer.println("#Version: 1.0");
>                 writer.println("#Software: " +
ServerInfo.getServerInfo());
>             }
>
>
>         } catch (IOException e) {
>             writer = null;
>             currentLogFile = null;
>         }
>
>     }
>
>
>     /**
>      * This method returns a Date object that is accurate to within one
>      * second.  If a thread calls this method to get a Date and it's been
>      * less than 1 second since a new Date was created, this method
>      * simply gives out the same Date again so that the system doesn't
>      * spend time creating Date objects unnecessarily.
>      */
>     private Date getDate(long systime) {
>         /* Avoid extra call to System.currentTimeMillis(); */
>         if (0==systime) {
>             systime = System.currentTimeMillis();
>         }
>
>         // Only create a new Date once per second, max.
>         if ((systime - currentDate.getTime()) > 1000) {
>             currentDate.setTime(systime);
>         }
>
>         return currentDate;
>
>     }
>
>
>     // ------------------------------------------------------ Lifecycle
Methods
>
>
>     /**
>      * Add a lifecycle event listener to this component.
>      *
>      * @param listener The listener to add
>      */
>     public void addLifecycleListener(LifecycleListener listener) {
>
>         lifecycle.addLifecycleListener(listener);
>
>     }
>
>
>     /**
>      * Get the lifecycle listeners associated with this lifecycle. If this
>      * Lifecycle has no listeners registered, a zero-length array is
returned.
>      */
>     public LifecycleListener[] findLifecycleListeners() {
>
>         return lifecycle.findLifecycleListeners();
>
>     }
>
>
>     /**
>      * Remove a lifecycle event listener from this component.
>      *
>      * @param listener The listener to add
>      */
>     public void removeLifecycleListener(LifecycleListener listener) {
>
>         lifecycle.removeLifecycleListener(listener);
>
>     }
>
>
>     /**
>      * Prepare for the beginning of active use of the public methods of
this
>      * component.  This method should be called after
<code>configure()</code>,
>      * and before any of the public methods of the component are utilized.
>      *
>      * @exception LifecycleException if this component detects a fatal
error
>      *  that prevents this component from being used
>      */
>     public void start() throws LifecycleException {
>
>         // Validate and update our current component state
>         if (started)
>             throw new LifecycleException
>                 (sm.getString("extendedAccessLogValve.alreadyStarted"));
>         lifecycle.fireLifecycleEvent(START_EVENT, null);
>         started = true;
>
>         // Initialize the timeZone, Date formatters, and currentDate
>         TimeZone tz = TimeZone.getTimeZone("GMT");
>         dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
>         dateFormatter.setTimeZone(tz);
>         timeFormatter = new SimpleDateFormat("HH:mm:ss");
>         timeFormatter.setTimeZone(tz);
>         currentDate = new Date(System.currentTimeMillis());
>         fileDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
>         dateStamp = fileDateFormatter.format(currentDate);
>
>         /* Everybody say ick ... ick */
>         try {
>             InetAddress inetAddress = InetAddress.getLocalHost();
>             myIpAddress = inetAddress.getHostAddress();
>             myDNSName = inetAddress.getHostName();
>         } catch(Throwable e){
>             myIpAddress="127.0.0.1";
>             myDNSName="localhost";
>         }
>
>         open();
>
>     }
>
>
>     /**
>      * Gracefully terminate the active use of the public methods of this
>      * component.  This method should be the last one called on a given
>      * instance of this component.
>      *
>      * @exception LifecycleException if this component detects a fatal
error
>      *  that needs to be reported
>      */
>     public void stop() throws LifecycleException {
>
>         // Validate and update our current component state
>         if (!started)
>             throw new LifecycleException
>                 (sm.getString("extendedAccessLogValve.notStarted"));
>         lifecycle.fireLifecycleEvent(STOP_EVENT, null);
>         started = false;
>
>         close();
>
>     }
>
>     /**
>      * Decode the given pattern. Is public so a pattern may
>      * allows to be validated.
>      * @param fields The pattern to decode
>      * @return null on error.  Otherwise array of decoded fields
>      */
>     public FieldInfo[] decodePattern(String fields) {
>         if (log.isDebugEnabled())
>             log.debug("decodePattern, fields=" + fields);
>
>         LinkedList list = new LinkedList();
>
>         //Ignore leading whitespace.
>         int i=0;
>         for (;i<fields.length() &&
Character.isWhitespace(fields.charAt(i));i++);
>
>         if (i>=fields.length()) {
>             log.info("fields was just empty or whitespace");
>             return null;
>         }
>
>         int j;
>         while(i<fields.length()) {
>             if (log.isDebugEnabled())
>                 log.debug("fields.substring(i)=" + fields.substring(i));
>
>             FieldInfo currentFieldInfo = new FieldInfo();
>
>
>             if (fields.startsWith("date",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
>                 currentFieldInfo.location = FieldInfo.SPECIAL_DATE;
>                 i+="date".length();
>             } else if (fields.startsWith("time-taken",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
>                 currentFieldInfo.location = FieldInfo.SPECIAL_TIME_TAKEN;
>                 i+="time-taken".length();
>             } else if (fields.startsWith("time",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
>                 currentFieldInfo.location = FieldInfo.SPECIAL_TIME;
>                 i+="time".length();
>             } else if (fields.startsWith("bytes",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
>                 currentFieldInfo.location = FieldInfo.SPECIAL_BYTES;
>                 i+="bytes".length();
>             } else if (fields.startsWith("cached",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
>                 currentFieldInfo.location = FieldInfo.SPECIAL_CACHED;
>                 i+="cached".length();
>             } else if (fields.startsWith("c-ip",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_CLIENT;
>                 currentFieldInfo.location = FieldInfo.FIELD_IP;
>                 i+="c-ip".length();
>             } else if (fields.startsWith("c-dns",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_CLIENT;
>                 currentFieldInfo.location = FieldInfo.FIELD_DNS;
>                 i+="c-dns".length();
>             } else if (fields.startsWith("s-ip",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SERVER;
>                 currentFieldInfo.location = FieldInfo.FIELD_IP;
>                 i+="s-ip".length();
>             } else if (fields.startsWith("s-dns",i)) {
>                 currentFieldInfo.type = FieldInfo.DATA_SERVER;
>                 currentFieldInfo.location = FieldInfo.FIELD_DNS;
>                 i+="s-dns".length();
>             } else if (fields.startsWith("cs",i)) {
>                 i = decode(fields, i+2, currentFieldInfo,
>                             FieldInfo.DATA_CLIENT_TO_SERVER);
>                 if (i<0)
>                     return null;
>             } else if (fields.startsWith("sc",i)) {
>                 i = decode(fields, i+2, currentFieldInfo,
>                             FieldInfo.DATA_SERVER_TO_CLIENT);
>                 if (i<0)
>                     return null;
>             } else if (fields.startsWith("sr",i)) {
>                 i = decode(fields, i+2, currentFieldInfo,
>                             FieldInfo.DATA_SERVER_TO_RSERVER);
>                 if (i<0)
>                     return null;
>             } else if (fields.startsWith("rs",i)) {
>                 i = decode(fields, i+2, currentFieldInfo,
>                             FieldInfo.DATA_RSERVER_TO_SERVER);
>                 if (i<0)
>                     return null;
>             } else if (fields.startsWith("x",i)) {
>                 i = decodeAppSpecific(fields, i, currentFieldInfo);
>             } else {
>                 // Unable to decode ...
>                 log.error("unable to decode with rest of chars being: " +
>                             fields.substring(i));
>                 return null;
>             }
>
>             // By this point we should have the field, get the whitespace
>             j=i;
>             for (;j<fields.length() &&
Character.isWhitespace(fields.charAt(j));j++);
>
>             if (j>=fields.length()) {
>                 if (j==i) {
>                     // Special case - end of string
>                     currentFieldInfo.postWhiteSpace = "";
>                 } else {
>                     currentFieldInfo.postWhiteSpace = fields.substring(i);
>                     i=j;
>                 }
>             } else {
>                 currentFieldInfo.postWhiteSpace = fields.substring(i,j);
>                 i=j;
>             }
>
>             list.add(currentFieldInfo);
>         }
>
>         i=0;
>         FieldInfo[] f = new FieldInfo[list.size()];
>         for (Iterator k = list.iterator(); k.hasNext();)
>              f[i++] = (FieldInfo)k.next();
>
>         if (log.isDebugEnabled())
>             log.debug("finished decoding with length of: " + i);
>
>         return f;
>     }
>
>     /**
>      * Decode the cs or sc fields.
>      * Returns negative on error.
>      *
>      * @param fields The pattern to decode
>      * @param i The string index where we are decoding.
>      * @param fieldInfo Where to store the results
>      * @param type The type we are decoding.
>      * @return -1 on error. Otherwise the new String index.
>      */
>     private int decode(String fields, int i, FieldInfo fieldInfo, short
type) {
>
>         if (fields.startsWith("-status",i)) {
>             fieldInfo.location = FieldInfo.FIELD_STATUS;
>             i+="-status".length();
>         } else if (fields.startsWith("-comment",i)) {
>             fieldInfo.location = FieldInfo.FIELD_COMMENT;
>             i+="-comment".length();
>         } else if (fields.startsWith("-uri-query",i)) {
>             fieldInfo.location = FieldInfo.FIELD_URI_QUERY;
>             i+="-uri-query".length();
>         } else if (fields.startsWith("-uri-stem",i)) {
>             fieldInfo.location = FieldInfo.FIELD_URI_STEM;
>             i+="-uri-stem".length();
>         } else if (fields.startsWith("-uri",i)) {
>             fieldInfo.location = FieldInfo.FIELD_URI;
>             i+="-uri".length();
>         } else if (fields.startsWith("-method",i)) {
>             fieldInfo.location = FieldInfo.FIELD_METHOD;
>             i+="-method".length();
>         } else if (fields.startsWith("(",i)) {
>             fieldInfo.location = FieldInfo.FIELD_HEADER;
>             i++;                                  /* Move past the ( */
>             int j = fields.indexOf(')', i);
>             if (j==-1) {                          /* Not found */
>                 log.error("No closing ) found for in decode");
>                 return -1;
>             }
>             fieldInfo.value = fields.substring(i,j);
>             i=j+1;                                // Move pointer past )
*/
>         } else {
>             log.error("The next characters couldn't be decoded: " +
fields.substring(i));
>             return -1;
>         }
>
>         fieldInfo.type = type;
>         return i;
>     }
>
>
>     /**
>       * Decode app specific log entry.
>       *
>       * Special fields are of the form:
>       * x-C(...) - For cookie
>       * x-A(...) - Value in servletContext
>       * x-S(...) - Value in session
>       * x-R(...) - Value in servletRequest
>       * @param fields The pattern to decode
>       * @param i The string index where we are decoding.
>       * @param fieldInfo Where to store the results
>       * @return -1 on error. Otherwise the new String index.
>       */
>     private int decodeAppSpecific(String fields, int i, FieldInfo
fieldInfo) {
>         fieldInfo.type = FieldInfo.DATA_APP_SPECIFIC;
>         /* Move past 'x-' */
>         i+=2;
>
>         if (i>=fields.length()) {
>             log.error("End of line reached before decoding x- param");
>             return -1;
>         }
>
>         switch(fields.charAt(i)) {
>             case 'A':
>                 fieldInfo.xType = FieldInfo.X_APP;
>                 break;
>             case 'C':
>                 fieldInfo.xType = FieldInfo.X_COOKIE;
>                 break;
>             case 'R':
>                 fieldInfo.xType = FieldInfo.X_REQUEST;
>                 break;
>             case 'S':
>                 fieldInfo.xType = FieldInfo.X_SESSION;
>                 break;
>             case 'H':
>                 fieldInfo.xType = FieldInfo.X_SERVLET_REQUEST;
>                 break;
>             default:
>                 return -1;
>         }
>
>         /* test that next char is a ( */
>         if (i+1!=fields.indexOf('(',i)) {
>             log.error("x param in wrong format. Needs to be 'x-#(...)'
read the docs!");
>             return -1;
>         }
>         i+=2; /* Move inside of the () */
>
>         /* Look for ending ) and return error if not found. */
>         int j = fields.indexOf(')',i);
>         if (j==-1) {
>             log.error("x param in wrong format. No closing ')'!");
>             return -1;
>         }
>
>         fieldInfo.value = fields.substring(i,j);
>
>         if (fieldInfo.xType == FieldInfo.X_SERVLET_REQUEST) {
>             if ("authType".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_AUTHTYPE;
>             } else if ("remoteUser".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_REMOTEUSER;
>             } else if ("requestedSessionId".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_REQUESTEDSESSIONID;
>             } else if
("requestedSessionIdFromCookie".equals(fieldInfo.value)){
>                 fieldInfo.location =
FieldInfo.X_LOC_REQUESTEDSESSIONIDFROMCOOKIE;
>             } else if ("requestedSessionIdValid".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_REQUESTEDSESSIONID;
>             } else if ("contentLength".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_CONTENTLENGTH;
>             } else if ("characterEncoding".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_CHARACTERENCODING;
>             } else if ("locale".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_LOCALE;
>             } else if ("protocol".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_PROTOCOL;
>             } else if ("scheme".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_SCHEME;
>             } else if ("secure".equals(fieldInfo.value)){
>                 fieldInfo.location = FieldInfo.X_LOC_SECURE;
>             } else {
>                 log.error("x param for servlet request, couldn't decode
value: " +
>                             fieldInfo.location);
>                 return -1;
>             }
>         }
>
>         return j+1;
>     }
>
>
>
> }
>
> /**
>  * A simple helper for decoding the pattern.
>  */
> class FieldInfo {
>     /*
>        The goal of the constants listed below is to make the construction
of the log
>        entry as quick as possible via numerci decodings of the methods to
call instead
>        of performing many String comparisons on each logging request.
>     */
>
>     /* Where the data is located. */
>     static final short DATA_CLIENT = 0;
>     static final short DATA_SERVER = 1;
>     static final short DATA_REMOTE = 2;
>     static final short DATA_CLIENT_TO_SERVER = 3;
>     static final short DATA_SERVER_TO_CLIENT = 4;
>     static final short DATA_SERVER_TO_RSERVER = 5; /* Here to honor the
spec. */
>     static final short DATA_RSERVER_TO_SERVER = 6; /* Here to honor the
spec. */
>     static final short DATA_APP_SPECIFIC = 7;
>     static final short DATA_SPECIAL = 8;
>
>     /* The type of special fields. */
>     static final short SPECIAL_DATE         = 1;
>     static final short SPECIAL_TIME_TAKEN   = 2;
>     static final short SPECIAL_TIME         = 3;
>     static final short SPECIAL_BYTES        = 4;
>     static final short SPECIAL_CACHED       = 5;
>
>     /* Where to pull the data for prefixed values */
>     static final short FIELD_IP            = 1;
>     static final short FIELD_DNS           = 2;
>     static final short FIELD_STATUS        = 3;
>     static final short FIELD_COMMENT       = 4;
>     static final short FIELD_METHOD        = 5;
>     static final short FIELD_URI           = 6;
>     static final short FIELD_URI_STEM      = 7;
>     static final short FIELD_URI_QUERY     = 8;
>     static final short FIELD_HEADER        = 9;
>
>
>     /* Application Specific parameters */
>     static final short X_REQUEST = 1; /* For x app specific */
>     static final short X_SESSION = 2; /* For x app specific */
>     static final short X_COOKIE  = 3; /* For x app specific */
>     static final short X_APP     = 4; /* For x app specific */
>     static final short X_SERVLET_REQUEST = 5; /* For x app specific */
>
>     static final short X_LOC_AUTHTYPE                       = 1;
>     static final short X_LOC_REMOTEUSER                     = 2;
>     static final short X_LOC_REQUESTEDSESSIONID             = 3;
>     static final short X_LOC_REQUESTEDSESSIONIDFROMCOOKIE   = 4;
>     static final short X_LOC_REQUESTEDSESSIONIDVALID        = 5;
>     static final short X_LOC_CONTENTLENGTH                  = 6;
>     static final short X_LOC_CHARACTERENCODING              = 7;
>     static final short X_LOC_LOCALE                         = 8;
>     static final short X_LOC_PROTOCOL                       = 9;
>     static final short X_LOC_SCHEME                         = 10;
>     static final short X_LOC_SECURE                         = 11;
>
>
>
>     /** The field type */
>     short type;
>
>     /** Where to pull the data from? Icky variable name. */
>     short location;
>
>     /** The x- specific place to pull the data from. */
>     short xType;
>
>     /** The field value if needed. Needed for headers and app specific. */
>     String value;
>
>     /** Any white space after this field? Put it here. */
>     String postWhiteSpace = null;
>
> }
>


----------------------------------------------------------------------------
----


> Index: mbeans-descriptors.xml
> ===================================================================
> RCS file:
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catali
na/valves/mbeans-descriptors.xml,v
> retrieving revision 1.1
> diff -u -r1.1 mbeans-descriptors.xml
> --- mbeans-descriptors.xml 17 Mar 2003 06:45:33 -0000 1.1
> +++ mbeans-descriptors.xml 1 Jun 2003 00:16:40 -0000
> @@ -108,19 +108,79 @@
>      <attribute name="debug"
>                 description="The debugging detail level for this
component"
>                 type="int"/>
> -
> +
>    </mbean>
> -
> +
> +  <mbean name="ExtendedAccessLogValve"
> +         description="Valve that generates a web server access log"
> +         domain="Catalina"
> +         group="Valve"
> +         type="org.apache.catalina.valves.ExtendedAccessLogValve">
> +
> +    <attribute name="className"
> +               description="Fully qualified class name of the managed
object"
> +               type="java.lang.String"
> +               writeable="false"/>
> +
> +    <attribute name="debug"
> +               description="The debugging detail level for this
component"
> +               type="int"/>
> +
> +    <attribute name="containerName"
> +               description="Object name of the container"
> +               type="javax.management.ObjectName"/>
> +
> +    <attribute name="directory"
> +               description="The directory in which log files are created"
> +               type="java.lang.String"/>
> +
> +    <attribute   name="pattern"
> +               description="The pattern used to format our access log
lines"
> +               type="java.lang.String"/>
> +
> +    <attribute name="prefix"
> +               description="The prefix that is added to log file
filenames"
> +               type="java.lang.String"/>
> +
> +    <attribute name="rotatable"
> +               description="Rotate log"
> +               is="true"
> +               type="boolean"/>
> +
> +    <attribute name="condition"
> +               description="The value to look for conditional logging."
> +               type="java.lang.String"/>
> +
> +    <attribute name="checkExists"
> +               description="Check for file existence before each
logging."
> +               is="true"
> +               type="boolean"/>
> +
> +    <attribute name="suffix"
> +               description="The suffix that is added to log file
filenames"
> +               type="java.lang.String"/>
> +
> +    <operation name="rotate"
> +               description="Move the existing log file to a new name"
> +               impact="ACTION"
> +               returnType="boolean">
> +      <parameter name="newFileName"
> +                 description="File name to move the log file to."
> +                 type="java.lang.String"/>
> +    </operation>
> +
> +  </mbean>
> +
>    <mbean name="RemoteAddrValve"
>           description="Concrete implementation of RequestFilterValve that
filters based on the string representation of the remote client's IP
address"
>           domain="Catalina"
>           group="Valve"
>           type="org.apache.catalina.valves.RemoteAddrValve">
> -
> +
>      <attribute name="allow"
>                 description="The comma-delimited set of allow expressions"
>                 type="java.lang.String"/>
> -
> +
>      <attribute name="containerName"
>                 description="Object name of the container"
>                 type="javax.management.ObjectName"/>
>
>


----------------------------------------------------------------------------
----


> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to