You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2007/01/06 19:48:19 UTC

svn commit: r493535 - /tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java

Author: remm
Date: Sat Jan  6 10:48:18 2007
New Revision: 493535

URL: http://svn.apache.org/viewvc?view=rev&rev=493535
Log:
- New AccessLog valve impl, submitted by Takayuki Kaneko.

Added:
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java   (with props)

Added: tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java?view=auto&rev=493535
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java Sat Jan  6 10:48:18 2007
@@ -0,0 +1,1294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.valves;
+
+
+import java.io.BufferedWriter;
+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.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.catalina.util.StringManager;
+
+
+/**
+ * <p>Implementation of the <b>Valve</b> interface that generates a web server
+ * access log with the detailed line contents matching a configurable pattern.
+ * The syntax of the available patterns is similar to that supported by the
+ * Apache <code>mod_log_config</code> module.  As an additional feature,
+ * automatic rollover of log files when the date changes is also supported.</p>
+ *
+ * <p>Patterns for the logged message may include constant text or any of the
+ * following replacement strings, for which the corresponding information
+ * from the specified Response is substituted:</p>
+ * <ul>
+ * <li><b>%a</b> - Remote IP address
+ * <li><b>%A</b> - Local IP address
+ * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
+ *     were sent
+ * <li><b>%B</b> - Bytes sent, excluding HTTP headers
+ * <li><b>%h</b> - Remote host name
+ * <li><b>%H</b> - Request protocol
+ * <li><b>%l</b> - Remote logical username from identd (always returns '-')
+ * <li><b>%m</b> - Request method
+ * <li><b>%p</b> - Local port
+ * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
+ *     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>%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> -
+ *   <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
+ * </ul>
+ *
+ * <p>
+ * There is also support to write information from the cookie, incoming
+ * header, the Session or something else in the ServletRequest.<br>
+ * It is modeled after the apache syntax:
+ * <ul>
+ * <li><code>%{xxx}i</code> for incoming headers
+ * <li><code>%{xxx}c</code> for a specific cookie
+ * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
+ * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
+ * </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
+ * @author Takayuki Kaneko
+ * @version $Revision: 467222 $ $Date: 2007-01-04 12:17:11 +0900
+ */
+
+public class FastAccessLogValve
+    extends ValveBase
+    implements Lifecycle {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this class with default property values.
+     */
+    public FastAccessLogValve() {
+        super();
+        setPattern("common");
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * 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 directory in which log files are created.
+     */
+    private String directory = "logs";
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.valves.FastAccessLogValve/1.0";
+
+
+    /**
+     * The lifecycle event support for this component.
+     */
+    protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+
+    /**
+     * The set of month abbreviations for log messages.
+     */
+    protected static final String months[] =
+    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+
+    /**
+     * 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 string manager for this package.
+     */
+    private StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Has this component been started yet?
+     */
+    private boolean started = false;
+
+
+    /**
+     * The suffix that is added to log file filenames.
+     */
+    private String suffix = "";
+
+
+    /**
+     * The PrintWriter to which we are currently logging, if any.
+     */
+    private PrintWriter writer = 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 Dates into a day string in the format
+     * "dd".
+     */
+    private SimpleDateFormat dayFormatter = null;
+
+
+    /**
+     * A date formatter to format a Date into a month string in the format
+     * "MM".
+     */
+    private SimpleDateFormat monthFormatter = null;
+
+
+    /**
+     * A date formatter to format a Date into a year string in the format
+     * "yyyy".
+     */
+    private SimpleDateFormat yearFormatter = 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;
+
+
+    /**
+     * The system timezone.
+     */
+    private TimeZone timezone = null;
+
+    
+    /**
+     * The time zone offset relative to GMT in text form when daylight saving
+     * is not in operation.
+     */
+    private String timeZoneNoDST = null;
+
+
+    /**
+     * The time zone offset relative to GMT in text form when daylight saving
+     * is in operation.
+     */
+    private String timeZoneDST = null;
+    
+    
+    /**
+     * The system time when we last updated the Date that this valve
+     * uses for log lines.
+     */
+    private Date currentDate = null;
+    
+    private long currentMillis = 0;
+
+
+    /**
+     * Resolve hosts.
+     */
+    private boolean resolveHosts = false;
+
+
+    /**
+     * Instant when the log daily rotation was last checked.
+     */
+    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;
+    
+    /**
+     * Array of AccessLogElement, they will be used to make log message.
+     */
+    private AccessLogElement[] logElements = 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 (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
+     */
+    public void setPattern(String pattern) {
+        if (pattern == null)
+            pattern = "";
+        if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
+            pattern = Constants.AccessLog.COMMON_PATTERN;
+        if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
+            pattern = Constants.AccessLog.COMBINED_PATTERN;
+        this.pattern = pattern;
+        logElements = createLogElements();
+    }
+
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * Set the resolve hosts flag.
+     *
+     * @param resolveHosts The new resolve hosts value
+     */
+    public void setResolveHosts(boolean resolveHosts) {
+        this.resolveHosts = resolveHosts;
+    }
+
+
+    /**
+     * Get the value of the resolve hosts flag.
+     */
+    public boolean isResolveHosts() {
+        return resolveHosts;
+    }
+
+
+    /**
+     * 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
+
+    /**
+     * Execute a periodic task, such as reloading, etc. This method will be
+     * invoked inside the classloading context of this container. Unexpected
+     * throwables will be caught and logged.
+     */
+    public void backgroundProcess() {
+        if (writer != null) {
+            writer.flush();
+        }
+    }    
+
+    /**
+     * 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
+     *
+     * @exception IOException if an input/output error has occurred
+     * @exception ServletException if a servlet error has occurred
+     */
+    public void invoke(Request request, Response response) throws IOException,
+            ServletException {
+
+        // Pass this request on to the next valve in our pipeline
+        long t1 = System.currentTimeMillis();
+
+        getNext().invoke(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();
+
+        for (int i = 0; i < logElements.length; i++) {
+            logElements[i].addElement(result, date, request, response, time);
+        }
+
+        log(result.toString());
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Close the currently open log file (if any)
+     */
+    private synchronized void close() {
+        if (writer == null) {
+            return;
+        }
+        writer.flush();
+        writer.close();
+        writer = null;
+        dateStamp = "";
+    }
+
+
+    /**
+     * 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
+     */
+    public void log(String message) {
+        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 = dateFormatter.format(currentDate);
+
+                // If the date has changed, switch log files
+                if (!dateStamp.equals(tsDate)) {
+                    synchronized (this) {
+                        if (!dateStamp.equals(tsDate)) {
+                            close();
+                            dateStamp = tsDate;
+                            open();
+                        }
+                    }
+                }
+            }
+        }
+
+        // Log this message
+        if (writer != null) {
+            writer.println(message);
+        }
+
+    }
+
+
+    /**
+     * Return the month abbreviation for the specified month, which must
+     * be a two-digit String.
+     *
+     * @param month Month number ("01" .. "12").
+     */
+    private String lookup(String month) {
+        int index;
+        try {
+            index = Integer.parseInt(month) - 1;
+        } catch (Throwable t) {
+            index = 0;  // Can not happen, in theory
+        }
+        return (months[index]);
+    }
+
+
+    /**
+     * 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;
+            }
+            writer = new PrintWriter(new BufferedWriter(new FileWriter(
+                    pathname, true), 128000), false);
+        } catch (IOException e) {
+            writer = 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.
+     * 
+     * @return Date
+     */
+    private Date getDate() {
+        // Only create a new Date once per second, max.
+        long systime = System.currentTimeMillis();
+        if ((systime - currentMillis) > 1000) {
+            synchronized (this) {
+                if ((systime - currentMillis) > 1000) {
+                    currentDate = new Date(systime);
+                    currentMillis = systime;
+                }
+            }
+        }
+        return currentDate;
+    }
+
+
+    private String getTimeZone(Date date) {
+        if (timezone.inDaylightTime(date)) {
+            return timeZoneDST;
+        } else {
+            return timeZoneNoDST;
+        }
+    }
+    
+    
+    private String calculateTimeZoneOffset(long offset) {
+        StringBuffer tz = new StringBuffer();
+        if ((offset < 0)) {
+            tz.append("-");
+            offset = -offset;
+        } else {
+            tz.append("+");
+        }
+
+        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
+
+
+    /**
+     * 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("accessLogValve.alreadyStarted"));
+        lifecycle.fireLifecycleEvent(START_EVENT, null);
+        started = true;
+
+        // Initialize the timeZone, Date formatters, and currentDate
+        timezone = TimeZone.getDefault();
+        timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
+        Calendar calendar = Calendar.getInstance(timezone);
+        int offset = calendar.get(Calendar.DST_OFFSET);
+        timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset);
+
+        if (fileDateFormat == null || fileDateFormat.length() == 0)
+            fileDateFormat = "yyyy-MM-dd";
+        dateFormatter = new SimpleDateFormat(fileDateFormat);
+        dateFormatter.setTimeZone(timezone);
+        dayFormatter = new SimpleDateFormat("dd");
+        dayFormatter.setTimeZone(timezone);
+        monthFormatter = new SimpleDateFormat("MM");
+        monthFormatter.setTimeZone(timezone);
+        yearFormatter = new SimpleDateFormat("yyyy");
+        yearFormatter.setTimeZone(timezone);
+        timeFormatter = new SimpleDateFormat("HH:mm:ss");
+        timeFormatter.setTimeZone(timezone);
+        currentDate = new Date();
+        dateStamp = dateFormatter.format(currentDate);
+        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("accessLogValve.notStarted"));
+        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+        started = false;
+        close();
+    }
+    
+    /**
+     * AccessLogElement writes the partial message into the buffer.
+     */
+    private interface AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time);
+
+    }
+    
+    /**
+     * write local IP address - %A
+     */
+    private class LocalAddrElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            String value;
+            try {
+                value = InetAddress.getLocalHost().getHostAddress();
+            } catch (Throwable e) {
+                value = "127.0.0.1";
+            }
+            buf.append(value);
+        }
+    }
+    
+    /**
+     * write remote IP address - %a
+     */
+    private class RemoteAddrElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getRemoteAddr());
+        }
+    }
+    
+    /**
+     * write remote host name - %h
+     */
+    private class HostElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getRemoteHost());
+        }
+    }
+    
+    /**
+     * write remote logical username from identd (always returns '-') - %l
+     */
+    private class LogicalUserNameElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append('-');
+        }
+    }
+    
+    /**
+     * write request protocol - %H
+     */
+    private class ProtocolElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getProtocol());
+        }
+    }
+
+    /**
+     * write remote user that was authenticated (if any), else '-' - %u
+     */
+    private class UserElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                String value = request.getRemoteUser();
+                if (value != null) {
+                    buf.append(value);
+                } else {
+                    buf.append('-');
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write date and time, in Common Log Format - %t
+     */
+    private class DateAndTimeElement implements AccessLogElement {
+        private Date currentDate = new Date(0);
+
+        private String currentDateString = null;
+        
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (currentDate != date) {
+                synchronized (this) {
+                    if (currentDate != date) {
+                        StringBuffer current = new StringBuffer(32);
+                        current.append('[');
+                        current.append(dayFormatter.format(date)); // Day
+                        current.append('/');
+                        current.append(lookup(monthFormatter.format(date))); // Month
+                        current.append('/');
+                        current.append(yearFormatter.format(date)); // Year
+                        current.append(':');
+                        current.append(timeFormatter.format(date)); // Time
+                        current.append(' ');
+                        current.append(getTimeZone(date)); // Timezone
+                        current.append(']');
+                        currentDateString = current.toString();
+                        currentDate = date;
+                    }
+                }
+            }
+            buf.append(currentDateString);
+        }
+    }
+
+    /**
+     * write first line of the request (method and request URI) - %r
+     */
+    private class RequestElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getMethod());
+                buf.append(' ');
+                buf.append(request.getRequestURI());
+                if (request.getQueryString() != null) {
+                    buf.append('?');
+                    buf.append(request.getQueryString());
+                }
+                buf.append(' ');
+                buf.append(request.getProtocol());
+            } else {
+                buf.append("- - ");
+                buf.append(request.getProtocol());
+            }
+        }
+    }
+
+    /**
+     * write HTTP status code of the response - %s
+     */
+    private class HttpStatusCodeElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (response != null) {
+                buf.append(response.getStatus());
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write local port on which this request was received - %p
+     */
+    private class LocalPortElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getServerPort());
+        }
+    }
+
+    /**
+     * write bytes sent, excluding HTTP headers - %b, %B
+     */
+    private class ByteSentElement implements AccessLogElement {
+        private boolean conversion;
+
+        /**
+         * if conversion is true, write '-' instead of 0 - %b
+         */
+        public ByteSentElement(boolean conversion) {
+            this.conversion = conversion;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            int length = response.getContentCount();
+            if (length <= 0 && conversion) {
+                buf.append('-');
+            } else {
+                buf.append(length);
+            }
+        }
+    }
+
+    /**
+     * write request method (GET, POST, etc.) - %m
+     */
+    private class MethodElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getMethod());
+            }
+        }
+    }
+
+    /**
+     * write time taken to process the request - %D, %T
+     */
+    private class ElapsedTimeElement implements AccessLogElement {
+        private boolean millis;
+
+        /**
+         * if millis is true, write time in millis - %D
+         * if millis is false, write time in seconds - %T
+         */
+        public ElapsedTimeElement(boolean millis) {
+            this.millis = millis;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (millis) {
+                buf.append(time);
+            } else {
+                // second
+                buf.append(time / 1000);
+                buf.append('.');
+                int remains = (int) (time % 1000);
+                buf.append(remains / 100);
+                remains = remains % 100;
+                buf.append(remains / 10);
+                buf.append(remains % 10);
+            }
+        }
+    }
+    
+    /**
+     * write Query string (prepended with a '?' if it exists) - %q
+     */
+    private class QueryElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            String query = null;
+            if (request != null)
+                query = request.getQueryString();
+            if (query != null) {
+                buf.append('?');
+                buf.append(query);
+            }
+        }
+    }
+
+    /**
+     * write user session ID - %S
+     */
+    private class SessionIdElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                if (request.getSession(false) != null) {
+                    buf.append(request.getSessionInternal(false)
+                            .getIdInternal());
+                } else {
+                    buf.append('-');
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write requested URL path - %U
+     */
+    private class RequestURIElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getRequestURI());
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write local server name - %v
+     */
+    private class LocalServerNameElement implements AccessLogElement {
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getServerName());
+        }
+    }
+    
+    /**
+     * write any string
+     */
+    private class StringElement implements AccessLogElement {
+        private String str;
+
+        public StringElement(String str) {
+            this.str = str;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(str);
+        }
+    }
+
+    /**
+     * write incoming headers - %{xxx}i
+     */
+    private class HeaderElement implements AccessLogElement {
+        private String header;
+
+        public HeaderElement(String header) {
+            this.header = header;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getHeader(header));
+        }
+    }
+
+    /**
+     * write a specific cookie - %{xxx}c
+     */
+    private class CookieElement implements AccessLogElement {
+        private String header;
+
+        public CookieElement(String header) {
+            this.header = header;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            String value = "-";
+            Cookie[] c = request.getCookies();
+            if (c != null) {
+                for (int i = 0; i < c.length; i++) {
+                    if (header.equals(c[i].getName())) {
+                        value = c[i].getValue();
+                        break;
+                    }
+                }
+            }
+            buf.append(value);
+        }
+    }
+
+    /**
+     * write an attribute in the ServletRequest - %{xxx}r
+     */
+    private class RequestAttributeElement implements AccessLogElement {
+        private String header;
+
+        public RequestAttributeElement(String header) {
+            this.header = header;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            Object value = null;
+            if (request != null) {
+                value = request.getAttribute(header);
+            } else {
+                value = "??";
+            }
+            if (value != null) {
+                if (value instanceof String) {
+                    buf.append((String) value);
+                } else {
+                    buf.append(value.toString());
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write an attribute in the HttpSession - %{xxx}s
+     */
+    private class SessionAttributeElement implements AccessLogElement {
+        private String header;
+
+        public SessionAttributeElement(String header) {
+            this.header = header;
+        }
+
+        public void addElement(StringBuffer buf, Date date, Request request,
+                Response response, long time) {
+            Object value = null;
+            if (null != request) {
+                HttpSession sess = request.getSession(false);
+                if (null != sess)
+                    value = sess.getAttribute(header);
+            } else {
+                value = "??";
+            }
+            if (value != null) {
+                if (value instanceof String) {
+                    buf.append((String) value);
+                } else {
+                    buf.append(value.toString());
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+
+
+
+    /**
+     * parse pattern string and create the array of AccessLogElement
+     */
+    private AccessLogElement[] createLogElements() {
+        List list = new ArrayList();
+        boolean replace = false;
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < pattern.length(); i++) {
+            char ch = pattern.charAt(i);
+            if (replace) {
+                /*
+                 * For code that processes {, the behavior will be ... if I do
+                 * not enounter a closing } - then I ignore the {
+                 */
+                if ('{' == ch) {
+                    StringBuffer name = new StringBuffer();
+                    int j = i + 1;
+                    for (; j < pattern.length() && '}' != pattern.charAt(j); j++) {
+                        name.append(pattern.charAt(j));
+                    }
+                    if (j + 1 < pattern.length()) {
+                        /* the +1 was to account for } which we increment now */
+                        j++;
+                        list.add(createAccessLogElement(name.toString(),
+                                pattern.charAt(j)));
+                        i = j; /* Since we walked more than one character */
+                    } else {
+                        // D'oh - end of string - pretend we never did this
+                        // and do processing the "old way"
+                        list.add(createAccessLogElement(ch));
+                    }
+                } else {
+                    list.add(createAccessLogElement(ch));
+                }
+                replace = false;
+            } else if (ch == '%') {
+                replace = true;
+                list.add(new StringElement(buf.toString()));
+                buf = new StringBuffer();
+            } else {
+                buf.append(ch);
+            }
+        }
+        if (buf.length() > 0) {
+            list.add(new StringElement(buf.toString()));
+        }
+        return (AccessLogElement[]) list.toArray(new AccessLogElement[0]);
+    }
+
+    /**
+     * create an AccessLogElement implementation which needs header string
+     */
+    private AccessLogElement createAccessLogElement(String header, char pattern) {
+        switch (pattern) {
+        case 'i':
+            return new HeaderElement(header);
+        case 'c':
+            return new CookieElement(header);
+        case 'r':
+            return new RequestAttributeElement(header);
+        case 's':
+            return new SessionAttributeElement(header);
+        default:
+            return new StringElement("???");
+        }
+    }
+
+    /**
+     * create an AccessLogElement implementation
+     */
+    private AccessLogElement createAccessLogElement(char pattern) {
+        switch (pattern) {
+        case 'a':
+            return new RemoteAddrElement();
+        case 'A':
+            return new LocalAddrElement();
+        case 'b':
+            return new ByteSentElement(true);
+        case 'B':
+            return new ByteSentElement(false);
+        case 'D':
+            return new ElapsedTimeElement(true);
+        case 'h':
+            return new HostElement();
+        case 'H':
+            return new ProtocolElement();
+        case 'l':
+            return new LogicalUserNameElement();
+        case 'm':
+            return new MethodElement();
+        case 'p':
+            return new LocalPortElement();
+        case 'q':
+            return new QueryElement();
+        case 'r':
+            return new RequestElement();
+        case 's':
+            return new HttpStatusCodeElement();
+        case 'S':
+            return new SessionIdElement();
+        case 't':
+            return new DateAndTimeElement();
+        case 'T':
+            return new ElapsedTimeElement(false);
+        case 'u':
+            return new UserElement();
+        case 'U':
+            return new RequestURIElement();
+        case 'v':
+            return new LocalServerNameElement();
+        default:
+            return new StringElement("???" + pattern + "???");
+        }
+    }
+}

Propchange: tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/FastAccessLogValve.java
------------------------------------------------------------------------------
    svn:eol-style = native



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