From: remm Date: Sat, 6 Jan 2007 18:48:18 +0000 (+0000) Subject: - New AccessLog valve impl, submitted by Takayuki Kaneko. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=e35f4d8efd0bbdce0d57575769016269d6461060;p=tomcat7.0 - New AccessLog valve impl, submitted by Takayuki Kaneko. git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@493535 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/java/org/apache/catalina/valves/FastAccessLogValve.java b/java/org/apache/catalina/valves/FastAccessLogValve.java new file mode 100644 index 000000000..d5bba8da1 --- /dev/null +++ b/java/org/apache/catalina/valves/FastAccessLogValve.java @@ -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; + + +/** + *

Implementation of the Valve 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 mod_log_config module. As an additional feature, + * automatic rollover of log files when the date changes is also supported.

+ * + *

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:

+ * + *

In addition, the caller can specify one of the following aliases for + * commonly utilized patterns:

+ * + * + *

+ * There is also support to write information from the cookie, incoming + * header, the Session or something else in the ServletRequest.
+ * It is modeled after the apache syntax: + *

+ *

+ * + *

+ * Conditional logging is also supported. This can be done with the + * condition property. + * If the value returned from ServletRequest.getAttribute(condition) + * yields a non-null value. The logging will be skipped. + *

+ * + * @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 pattern 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 dateStamp. + */ + 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 configure(), + * 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 + "???"); + } + } +}