You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by fu...@apache.org on 2003/07/09 01:12:52 UTC

cvs commit: jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/valves ExtendedAccessLogValve.java AccessLogValve.java

funkman     2003/07/08 16:12:51

  Modified:    catalina/src/share/org/apache/catalina/mbeans
                        mbeans-descriptors.xml
               catalina/src/share/org/apache/catalina/valves
                        AccessLogValve.java
  Added:       catalina/src/share/org/apache/catalina/valves
                        ExtendedAccessLogValve.java
  Log:
  Backport changes from tomcat 5
  This is literally a copy from the 5 version
   - New (from 5) ExtendAccessLogValve
   - Fix bz - 20380 AccessLogValve incorrectly calculates timezone
   - Enc bz - 16374 Date in file name configurable
   - Enc bz - 16400 Allow logging to be conditional
  
  Also get Costin's %D
  
  Revision  Changes    Path
  1.76      +116 -46   jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/mbeans/mbeans-descriptors.xml
  
  Index: mbeans-descriptors.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/mbeans/mbeans-descriptors.xml,v
  retrieving revision 1.75
  retrieving revision 1.76
  diff -u -r1.75 -r1.76
  --- mbeans-descriptors.xml	19 Feb 2003 01:04:44 -0000	1.75
  +++ mbeans-descriptors.xml	8 Jul 2003 23:12:51 -0000	1.76
  @@ -10,53 +10,60 @@
    -->
   
   <mbeans-descriptors>
  -
  -
  -  <mbean         name="AccessLogValve"
  -            className="org.apache.catalina.mbeans.ClassNameMBean"
  -          description="Valve that generates a web server access log"
  -               domain="Catalina"
  -                group="Valve"
  -                 type="org.apache.catalina.valves.AccessLogValve">
  -
  -    <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="directory"
  -          description="The directory in which log files are created"
  -                 type="java.lang.String"/>
  +  <mbean name="AccessLogValve"
  +         description="Valve that generates a web server access log"
  +         domain="Catalina"
  +         group="Valve"
  +         type="org.apache.catalina.valves.AccessLogValve">
  +
  +    <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="resolveHosts"
  -          description="Resolve hosts"
  -                   is="true"
  -                 type="boolean"/>
  -                 
  -    <attribute   name="rotatable"
  -          description="Rotate log"
  -                   is="true"
  -                 type="boolean"/>
  -                 
  -    <attribute   name="suffix"
  -          description="The suffix that is added to log file filenames"
  -                 type="java.lang.String"/>
  +               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="resolveHosts"
  +               description="Resolve hosts"
  +               is="true"
  +               type="boolean"/>
  +
  +    <attribute name="rotatable"
  +               description="Flag to indicate automatic log rotation."
  +               is="true"
  +               type="boolean"/>
  +
  +    <attribute name="suffix"
  +               description="The suffix that is added to log file filenames"
  +               type="java.lang.String"/>
  +
  +    <attribute name="condition"
  +               description="The value to look for conditional logging."
  +               type="java.lang.String"/>
  +
  +    <attribute name="fileDateFormat"
  +               description="The format for the date date based log rotation."
  +               type="java.lang.String"/>
     </mbean>
   
  -
     <mbean         name="BasicAuthenticator"
               className="org.apache.catalina.mbeans.ClassNameMBean"
             description="An Authenticator and Valve implementation of HTTP BASIC
  @@ -320,7 +327,7 @@
                     type="boolean"/>
   
       <attribute    name="disableUploadTimeout"
  -           description="Should Tomcat ignore setting a timeout for uploads?" 
  +           description="Should Tomcat ignore setting a timeout for uploads?"
                     type="boolean"/>
   
     </mbean>
  @@ -350,7 +357,7 @@
                    type="boolean"/>
   
       <attribute   name="swallowOutput"
  -          description="Flag to set to cause the system.out and system.err 
  +          description="Flag to set to cause the system.out and system.err
         to be redirected to the logger when executing a servlet"
                    type="boolean"/>
   
  @@ -491,6 +498,69 @@
   
     </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"/>
  +
  +    <attribute name="fileDateFormat"
  +               description="The format for the date date based log rotation."
  +               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="FileLogger"
               className="org.apache.catalina.mbeans.ClassNameMBean"
  @@ -1990,7 +2060,7 @@
               writeable="false"/>
   
       <attribute   name="swallowOutput"
  -          description="Flag to set to cause the system.out and system.err 
  +          description="Flag to set to cause the system.out and system.err
         to be redirected to the logger when executing a servlet"
                    type="boolean"/>
   
  
  
  
  1.16      +127 -23   jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/valves/AccessLogValve.java
  
  Index: AccessLogValve.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/valves/AccessLogValve.java,v
  retrieving revision 1.15
  retrieving revision 1.16
  diff -u -r1.15 -r1.16
  --- AccessLogValve.java	22 Nov 2002 20:27:12 -0000	1.15
  +++ AccessLogValve.java	8 Jul 2003 23:12:51 -0000	1.16
  @@ -67,6 +67,7 @@
   import java.io.PrintWriter;
   import java.net.InetAddress;
   import java.text.SimpleDateFormat;
  +import java.text.DecimalFormat;
   import java.util.Date;
   import java.util.TimeZone;
   import javax.servlet.ServletException;
  @@ -113,17 +114,19 @@
    *     an empty string
    * <li><b>%r</b> - First line of the request
    * <li><b>%s</b> - HTTP status code of the response
  - * <li><b>%S</b> - User session ID 
  + * <li><b>%S</b> - User session ID
    * <li><b>%t</b> - Date and time, in Common Log Format format
    * <li><b>%u</b> - Remote user that was authenticated
    * <li><b>%U</b> - Requested URL path
    * <li><b>%v</b> - Local server name
  + * <li><b>%D</b> - Time taken to process the request, in millis
  + * <li><b>%T</b> - Time taken to process the request, in seconds
    * </ul>
    * <p>In addition, the caller can specify one of the following aliases for
    * commonly utilized patterns:</p>
    * <ul>
    * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
  - * <li><b>combined</b> - 
  + * <li><b>combined</b> -
    *   <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
    * </ul>
    *
  @@ -139,6 +142,13 @@
    * </ul>
    * </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 Craig R. McClanahan
    * @author Jason Brittain
    * @version $Revision$ $Date$
  @@ -281,6 +291,12 @@
   
   
       /**
  +     * Time taken formatter for 3 decimal places.
  +     */
  +     private DecimalFormat timeTakenFormatter = null;
  +
  +
  +    /**
        * A date formatter to format a Date into a year string in the format
        * "yyyy".
        */
  @@ -325,6 +341,17 @@
       private long rotationLastChecked = 0L;
   
   
  +    /**
  +     * Are we doing conditional logging. default false.
  +     */
  +    private String condition = null;
  +
  +
  +    /**
  +     * Date format to place in log file name. Use at your own risk!
  +     */
  +    private String fileDateFormat = null;
  +
       // ------------------------------------------------------------- Properties
   
   
  @@ -418,27 +445,27 @@
           this.prefix = prefix;
   
       }
  -    
  -    
  +
  +
       /**
        * Should we rotate the logs
        */
       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;
  - 
  +
       }
   
   
  @@ -486,6 +513,45 @@
       }
   
   
  +    /**
  +     * 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;
  +
  +    }
  +
  +    /**
  +     *  Return the date format date based log rotation.
  +     */
  +    public String getFileDateFormat() {
  +        return fileDateFormat;
  +    }
  +
  +
  +    /**
  +     *  Set the date format date based log rotation.
  +     */
  +    public void setFileDateFormat(String fileDateFormat) {
  +        this.fileDateFormat =  fileDateFormat;
  +    }
  +
       // --------------------------------------------------------- Public Methods
   
   
  @@ -506,8 +572,19 @@
           throws IOException, ServletException {
   
           // Pass this request on to the next valve in our pipeline
  +        long t1=System.currentTimeMillis();
  +
           context.invokeNext(request, response);
   
  +        long t2=System.currentTimeMillis();
  +        long time=t2-t1;
  +
  +        if (condition!=null &&
  +                null!=request.getRequest().getAttribute(condition)) {
  +            return;
  +        }
  +
  +
           Date date = getDate();
           StringBuffer result = new StringBuffer();
   
  @@ -617,10 +694,10 @@
                           } else {
                               //D'oh - end of string - pretend we never did this
                               //and do processing the "old way"
  -                            result.append(replace(ch, date, request, response));
  +                            result.append(replace(ch, date, request, response, time));
                           }
                       } else {
  -                        result.append(replace(ch, date, request, response));
  +                        result.append(replace(ch, date, request, response,time ));
                       }
                       replace = false;
                   } else if (ch == '%') {
  @@ -682,7 +759,7 @@
                               close();
                               dateStamp = tsDate;
                               open();
  -                        }                        
  +                        }
                       }
                   }
   
  @@ -737,7 +814,7 @@
               } else {
                   pathname = dir.getAbsolutePath() + File.separator +
                               prefix + suffix;
  -            }    
  +            }
               writer = new PrintWriter(new FileWriter(pathname, true), true);
           } catch (IOException e) {
               writer = null;
  @@ -756,7 +833,7 @@
        * @param response Response being processed
        */
       private String replace(char pattern, Date date, Request request,
  -                           Response response) {
  +                           Response response, long time) {
   
           String value = null;
   
  @@ -776,7 +853,7 @@
                   value = InetAddress.getLocalHost().getHostAddress();
               } catch(Throwable e){
                   value = "127.0.0.1";
  -            }                        
  +            }
           } else if (pattern == 'b') {
               int length = response.getContentCount();
               if (length <= 0)
  @@ -798,6 +875,8 @@
                   value = "";
           } else if (pattern == 'p') {
               value = "" + req.getServerPort();
  +        } else if (pattern == 'D') {
  +                    value = "" + time;
           } else if (pattern == 'q') {
               String query = null;
               if (hreq != null)
  @@ -848,6 +927,8 @@
               temp.append(timeZone);                              // Timezone
               temp.append(']');
               value = temp.toString();
  +        } else if (pattern == 'T') {
  +            value = timeTakenFormatter.format(time/1000d);
           } else if (pattern == 'u') {
               if (hreq != null)
                   value = hreq.getRemoteUser();
  @@ -963,6 +1044,28 @@
       }
   
   
  +    private String calculateTimeZoneOffset(long offset) {
  +        StringBuffer tz = new StringBuffer();
  +        if ((offset<0))  {
  +            tz.append("-");
  +            offset = -offset;
  +        }
  +
  +        long hourOffset = offset/(1000*60*60);
  +        long minuteOffset = (offset/(1000*60)) % 60;
  +
  +        if (hourOffset<10)
  +            tz.append("0");
  +        tz.append(hourOffset);
  +
  +        if (minuteOffset<10)
  +            tz.append("0");
  +        tz.append(minuteOffset);
  +
  +        return tz.toString();
  +    }
  +
  +
       // ------------------------------------------------------ Lifecycle Methods
   
   
  @@ -979,7 +1082,7 @@
   
   
       /**
  -     * Get the lifecycle listeners associated with this lifecycle. If this 
  +     * Get the lifecycle listeners associated with this lifecycle. If this
        * Lifecycle has no listeners registered, a zero-length array is returned.
        */
       public LifecycleListener[] findLifecycleListeners() {
  @@ -1020,11 +1123,11 @@
   
           // Initialize the timeZone, Date formatters, and currentDate
           TimeZone tz = TimeZone.getDefault();
  -        timeZone = "" + (tz.getRawOffset() / (60 * 60 * 10));
  -        if (timeZone.length() < 5)
  -            timeZone = timeZone.substring(0, 1) + "0" +
  -                timeZone.substring(1, timeZone.length());
  -        dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
  +        timeZone = calculateTimeZoneOffset(tz.getRawOffset());
  +
  +        if (fileDateFormat==null || fileDateFormat.length()==0)
  +            fileDateFormat = "yyyy-MM-dd";
  +        dateFormatter = new SimpleDateFormat(fileDateFormat);
           dateFormatter.setTimeZone(tz);
           dayFormatter = new SimpleDateFormat("dd");
           dayFormatter.setTimeZone(tz);
  @@ -1036,6 +1139,7 @@
           timeFormatter.setTimeZone(tz);
           currentDate = new Date();
           dateStamp = dateFormatter.format(currentDate);
  +        timeTakenFormatter = new DecimalFormat("0.000");
   
           open();
   
  
  
  
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/valves/ExtendedAccessLogValve.java
  
  Index: ExtendedAccessLogValve.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/valves/ExtendedAccessLogValve.java,v 1.1 2003/07/08 23:12:51 funkman Exp $
   * $Revision: 1.1 $
   * $Date: 2003/07/08 23:12:51 $
   * ====================================================================
   *
   * 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 apache@apache.org.
   *
   * 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.net.URLEncoder;
  import java.text.SimpleDateFormat;
  import java.text.DecimalFormat;
  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 (in seconds) taken to serve 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><code>x-P(...)</code>:  Call request.getParameter(...)
   *                             and URLencode it. Helpful to capture
   *                             certain POST parameters.
   * </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>
   *
   * <p>
   * For extended attributes coming from a getAttribute() call,
   * it is you responsibility to ensure there are no newline or
   * control characters.
   * </p>
   *
   *
   * @author Tim Funk
   * @version $Revision: 1.1 $ $Date: 2003/07/08 23:12:51 $
   */
  
  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;
  
  
      /**
       * Time taken formatter for 3 decimal places.
       */
       private DecimalFormat timeTakenFormatter = 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;
  
  
      /**
       * Date format to place in log file name. Use at your own risk!
       */
      private String fileDateFormat = null;
  
  
      // ------------------------------------------------------------- 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;
  
      }
  
  
      /**
       *  Return the date format date based log rotation.
       */
      public String getFileDateFormat() {
          return fileDateFormat;
      }
  
  
      /**
       *  Set the date format date based log rotation.
       */
      public void setFileDateFormat(String fileDateFormat) {
          this.fileDateFormat =  fileDateFormat;
      }
  
  
      // --------------------------------------------------------- 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(timeTakenFormatter.format(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_PARAMETER:
                  return wrap(urlEncode(sr.getParameter(fieldInfo.value)));
              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 "-";
  
      }
  
  
      /**
       *  urlEncode the given string. If null or empty, return null.
       */
      private String urlEncode(String value) {
          if (null==value || value.length()==0) {
              return null;
          }
          return URLEncoder.encode(value);
      }
  
  
      /**
       *  Wrap the incoming value into quotes and escape any inner
       *  quotes with double quotes.
       *
       *  @param value - The value to wrap quotes around
       *  @return '-' if empty of null. Otherwise, toString() will
       *     be called on the object and the value will be wrapped
       *     in quotes and any quotes will be escaped with 2
       *     sets of quotes.
       */
      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());
          if (fileDateFormat==null || fileDateFormat.length()==0)
              fileDateFormat = "yyyy-MM-dd";
          fileDateFormatter = new SimpleDateFormat(fileDateFormat);
          dateStamp = fileDateFormatter.format(currentDate);
          timeTakenFormatter = new DecimalFormat("0.000");
  
          /* 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;
              case 'P':
                  fieldInfo.xType = FieldInfo.X_PARAMETER;
                  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_PARAMETER = 6; /* 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;
  
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-dev-help@jakarta.apache.org


Re: AccessLogValve.java - Missing +

Posted by Tim Funk <fu...@joedog.org>.
Doh! Your right. ( If its supposed to mimic the apache's %t, which I think it 
should)

-Tim

Yuri Schimke wrote:
> Funkman, Thanks for fixing the timezone calculation bug.  I know I'm
> just being picky now, but shouldn't positive timezone offsets have a
> (redundant) '+' at the start.
> 
>>>From http://httpd.apache.org/docs/logs.html#common
>         


---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-dev-help@jakarta.apache.org


AccessLogValve.java - Missing +

Posted by Yuri Schimke <yu...@aqris.com>.
Funkman, Thanks for fixing the timezone calculation bug.  I know I'm
just being picky now, but shouldn't positive timezone offsets have a
(redundant) '+' at the start.

>>From http://httpd.apache.org/docs/logs.html#common
        
        [10/Oct/2000:13:55:36 -0700] (%t)
                The time that the server finished processing the
                request. The format is: 
                        [day/month/year:hour:minute:second zone]
                        day = 2*digit
                        month = 3*letter
                        year = 4*digit
                        hour = 2*digit
                        minute = 2*digit
                        second = 2*digit
                        zone = (`+' | `-') 4*digit
                It is possible to have the time displayed in another
                format by specifying %{format}t in the log format
                string, where format is as in strftime(3) from the C
                standard library.
        

On Wed, 2003-07-09 at 02:12, funkman@apache.org wrote:

>    
>    
>   +    private String calculateTimeZoneOffset(long offset) {
>   +        StringBuffer tz = new StringBuffer();
>   +        if ((offset<0))  {
>   +            tz.append("-");
>   +            offset = -offset;
>   +        }
>   +
>   +        long hourOffset = offset/(1000*60*60);
>   +        long minuteOffset = (offset/(1000*60)) % 60;
>   +
>   +        if (hourOffset<10)
>   +            tz.append("0");
>   +        tz.append(hourOffset);
>   +
>   +        if (minuteOffset<10)
>   +            tz.append("0");
>   +        tz.append(minuteOffset);
>   +
>   +        return tz.toString();
>   +    }
>   +

--
Yuri Schimke
Aqris Software
+372 53 415 579

---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-dev-help@jakarta.apache.org