blob: 9a006bbe7e735520a1ae25c396aa692bacfee13f [file] [log] [blame]
/*
* 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.Date;
import java.util.Iterator;
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.AccessLog;
import org.apache.catalina.Globals;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.coyote.RequestInfo;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
/**
* <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
* <li><b>%I</b> - current Request thread name (can compare later with stacktraces)
* </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}o</code> for outgoing response 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 Remy Maucherat
* @author Takayuki Kaneko
* @author Peter Rossbach
*
* @version $Id: AccessLogValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
*/
public class AccessLogValve extends ValveBase implements AccessLog {
private static final Log log = LogFactory.getLog(AccessLogValve.class);
//------------------------------------------------------ Constructor
public AccessLogValve() {
super(true);
}
// ----------------------------------------------------- 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 volatile 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.AccessLogValve/2.1";
/**
* 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" };
/**
* enabled this component
*/
protected boolean enabled = true;
/**
* The pattern used to format our access log lines.
*/
protected String pattern = null;
/**
* The prefix that is added to log file filenames.
*/
protected String prefix = "access_log.";
/**
* Should we rotate our log file? Default is true (like old behavior)
*/
protected boolean rotatable = true;
/**
* Buffered logging.
*/
private boolean buffered = true;
/**
* The suffix that is added to log file filenames.
*/
protected String suffix = "";
/**
* The PrintWriter to which we are currently logging, if any.
*/
protected PrintWriter writer = null;
/**
* A date formatter to format a Date into a date in the format
* "yyyy-MM-dd".
*/
protected SimpleDateFormat fileDateFormatter = null;
/**
* The system timezone.
*/
private volatile TimeZone timezone = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is not in operation.
*/
private volatile String timeZoneNoDST = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is in operation.
*/
private volatile String timeZoneDST = null;
/**
* The current log file we are writing to. Helpful when checkExists
* is true.
*/
protected File currentLogFile = null;
private static class AccessDateStruct {
private Date currentDate = new Date();
private String currentDateString = null;
private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");
public AccessDateStruct() {
TimeZone tz = TimeZone.getDefault();
dayFormatter.setTimeZone(tz);
monthFormatter.setTimeZone(tz);
yearFormatter.setTimeZone(tz);
timeFormatter.setTimeZone(tz);
}
}
/**
* The system time when we last updated the Date that this valve
* uses for log lines.
*/
private static final ThreadLocal<AccessDateStruct> currentDateStruct =
new ThreadLocal<AccessDateStruct>() {
@Override
protected AccessDateStruct initialValue() {
return new AccessDateStruct();
}
};
/**
* Resolve hosts.
*/
private boolean resolveHosts = false;
/**
* Instant when the log daily rotation was last checked.
*/
private volatile long rotationLastChecked = 0L;
/**
* 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;
/**
* Are we doing conditional logging. default false.
*/
protected String condition = null;
/**
* Date format to place in log file name. Use at your own risk!
*/
protected String fileDateFormat = null;
/**
* Array of AccessLogElement, they will be used to make log message.
*/
protected AccessLogElement[] logElements = null;
/**
* @see #setRequestAttributesEnabled(boolean)
*/
protected boolean requestAttributesEnabled = false;
// ------------------------------------------------------------- Properties
/**
* @return Returns the enabled.
*/
public boolean getEnabled() {
return enabled;
}
/**
* {@inheritDoc}
*/
@Override
public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
this.requestAttributesEnabled = requestAttributesEnabled;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getRequestAttributesEnabled() {
return requestAttributesEnabled;
}
/**
* @param enabled
* The enabled to set.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* 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.
*/
@Override
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)
this.pattern = "";
else if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
this.pattern = Constants.AccessLog.COMMON_PATTERN;
else if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
this.pattern = Constants.AccessLog.COMBINED_PATTERN;
else
this.pattern = pattern;
logElements = createLogElements();
}
/**
* 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 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;
}
/**
* Is the logging buffered
*/
public boolean isBuffered() {
return buffered;
}
/**
* Set the value if the logging should be buffered
*
* @param buffered true if buffered.
*/
public void setBuffered(boolean buffered) {
this.buffered = buffered;
}
/**
* 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 logging. 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.
*/
@Override
public synchronized void backgroundProcess() {
if (getState().isAvailable() && getEnabled() && writer != null &&
buffered) {
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
*/
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
getNext().invoke(request, response);
}
@Override
public void log(Request request, Response response, long time) {
if (!getState().isAvailable() || !getEnabled() ||
logElements == null || condition != null
&& null != request.getRequest().getAttribute(condition)) {
return;
}
Date date = getDate();
StringBuilder result = new StringBuilder(128);
for (int i = 0; i < logElements.length; i++) {
logElements[i].addElement(result, date, request, response, time);
}
log(result.toString());
}
/**
* 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) {
ExceptionUtils.handleThrowable(e);
log.error(sm.getString("accessLogValve.rotateFail"), e);
}
/* Make sure date is correct */
dateStamp = fileDateFormatter.format(
new Date(System.currentTimeMillis()));
open();
return true;
} else {
return false;
}
}
// -------------------------------------------------------- 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 = "";
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
*/
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) {
synchronized(this) {
if ((systime - rotationLastChecked) > 1000) {
rotationLastChecked = systime;
String tsDate;
// Check for a change of date
tsDate = fileDateFormatter.format(new Date(systime));
// If the date has changed, switch log files
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) {
ExceptionUtils.handleThrowable(e);
log.info(sm.getString("accessLogValve.closeFail"), e);
}
/* Make sure date is correct */
dateStamp = fileDateFormatter.format(
new Date(System.currentTimeMillis()));
open();
}
}
}
// Log this message
synchronized(this) {
if (writer != null) {
writer.println(message);
if (!buffered) {
writer.flush();
}
}
}
}
/**
* 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) {
ExceptionUtils.handleThrowable(t);
index = 0; // Can not happen, in theory
}
return (months[index]);
}
/**
* Open the new log file for the date specified by <code>dateStamp</code>.
*/
protected synchronized void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())
dir = new File(System.getProperty(Globals.CATALINA_BASE_PROP), directory);
if (!dir.exists()) {
if (!dir.mkdirs()) {
log.error(sm.getString("accessLogValve.openDirFail", dir));
}
}
// 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);
currentLogFile = new File(pathname);
} 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.
*
* @return Date
*/
private Date getDate() {
// Only create a new Date once per second, max.
long systime = System.currentTimeMillis();
AccessDateStruct struct = currentDateStruct.get();
if ((systime - struct.currentDate.getTime()) > 1000) {
struct.currentDate.setTime(systime);
struct.currentDateString = null;
}
return struct.currentDate;
}
private String getTimeZone(Date date) {
if (timezone.inDaylightTime(date)) {
return timeZoneDST;
} else {
return timeZoneNoDST;
}
}
private String calculateTimeZoneOffset(long offset) {
StringBuilder tz = new StringBuilder();
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();
}
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Initialize the timeZone, Date formatters, and currentDate
TimeZone tz = TimeZone.getDefault();
timezone = tz;
timeZoneNoDST = calculateTimeZoneOffset(tz.getRawOffset());
int offset = tz.getDSTSavings();
timeZoneDST = calculateTimeZoneOffset(tz.getRawOffset() + offset);
String format = getFileDateFormat();
if (format == null || format.length() == 0) {
format = "yyyy-MM-dd";
setFileDateFormat(format);
}
fileDateFormatter = new SimpleDateFormat(format);
fileDateFormatter.setTimeZone(tz);
dateStamp = fileDateFormatter.format(currentDateStruct.get().currentDate);
open();
setState(LifecycleState.STARTING);
}
/**
* Stop this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
close();
}
/**
* AccessLogElement writes the partial message into the buffer.
*/
protected interface AccessLogElement {
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time);
}
/**
* write thread name - %I
*/
protected static class ThreadNameElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
RequestInfo info = request.getCoyoteRequest().getRequestProcessor();
if(info != null) {
buf.append(info.getWorkerThreadName());
} else {
buf.append("-");
}
}
}
/**
* write local IP address - %A
*/
protected static class LocalAddrElement implements AccessLogElement {
private static final String LOCAL_ADDR_VALUE;
static {
String init;
try {
init = InetAddress.getLocalHost().getHostAddress();
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
init = "127.0.0.1";
}
LOCAL_ADDR_VALUE = init;
}
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
buf.append(LOCAL_ADDR_VALUE);
}
}
/**
* write remote IP address - %a
*/
protected class RemoteAddrElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (requestAttributesEnabled) {
Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
if (addr == null) {
buf.append(request.getRemoteAddr());
} else {
buf.append(addr);
}
} else {
buf.append(request.getRemoteAddr());
}
}
}
/**
* write remote host name - %h
*/
protected class HostElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (requestAttributesEnabled) {
Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE);
if (host == null) {
buf.append(request.getRemoteHost());
} else {
buf.append(host);
}
} else {
buf.append(request.getRemoteHost());
}
}
}
/**
* write remote logical username from identd (always returns '-') - %l
*/
protected static class LogicalUserNameElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
buf.append('-');
}
}
/**
* write request protocol - %H
*/
protected class ProtocolElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (requestAttributesEnabled) {
Object proto = request.getAttribute(PROTOCOL_ATTRIBUTE);
if (proto == null) {
buf.append(request.getProtocol());
} else {
buf.append(proto);
}
} else {
buf.append(request.getProtocol());
}
}
}
/**
* write remote user that was authenticated (if any), else '-' - %u
*/
protected static class UserElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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
*/
protected class DateAndTimeElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
AccessDateStruct struct = currentDateStruct.get();
if (struct.currentDateString == null) {
StringBuilder current = new StringBuilder(32);
current.append('[');
current.append(struct.dayFormatter.format(date));
current.append('/');
current.append(lookup(struct.monthFormatter.format(date)));
current.append('/');
current.append(struct.yearFormatter.format(date));
current.append(':');
current.append(struct.timeFormatter.format(date));
current.append(' ');
current.append(getTimeZone(date));
current.append(']');
struct.currentDateString = current.toString();
}
buf.append(struct.currentDateString);
}
}
/**
* write first line of the request (method and request URI) - %r
*/
protected static class RequestElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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("- - ");
}
}
}
/**
* write HTTP status code of the response - %s
*/
protected static class HttpStatusCodeElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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
*/
protected class LocalPortElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (requestAttributesEnabled) {
Object port = request.getAttribute(SERVER_PORT_ATTRIBUTE);
if (port == null) {
buf.append(request.getServerPort());
} else {
buf.append(port);
}
} else {
buf.append(request.getServerPort());
}
}
}
/**
* write bytes sent, excluding HTTP headers - %b, %B
*/
protected static class ByteSentElement implements AccessLogElement {
private boolean conversion;
/**
* if conversion is true, write '-' instead of 0 - %b
*/
public ByteSentElement(boolean conversion) {
this.conversion = conversion;
}
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
// Don't need to flush since trigger for log message is after the
// response has been committed
long length = response.getBytesWritten(false);
if (length <= 0 && conversion) {
buf.append('-');
} else {
buf.append(length);
}
}
}
/**
* write request method (GET, POST, etc.) - %m
*/
protected static class MethodElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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
*/
protected static 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;
}
@Override
public void addElement(StringBuilder 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
*/
protected static class QueryElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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
*/
protected static class SessionIdElement implements AccessLogElement {
@Override
public void addElement(StringBuilder 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
*/
protected static class RequestURIElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (request != null) {
buf.append(request.getRequestURI());
} else {
buf.append('-');
}
}
}
/**
* write local server name - %v
*/
protected static class LocalServerNameElement implements AccessLogElement {
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
buf.append(request.getServerName());
}
}
/**
* write any string
*/
protected static class StringElement implements AccessLogElement {
private String str;
public StringElement(String str) {
this.str = str;
}
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
buf.append(str);
}
}
/**
* write incoming headers - %{xxx}i
*/
protected static class HeaderElement implements AccessLogElement {
private String header;
public HeaderElement(String header) {
this.header = header;
}
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
String value = request.getHeader(header);
if (value == null) {
buf.append('-');
} else {
buf.append(value);
}
}
}
/**
* write a specific cookie - %{xxx}c
*/
protected static class CookieElement implements AccessLogElement {
private String header;
public CookieElement(String header) {
this.header = header;
}
@Override
public void addElement(StringBuilder 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 a specific response header - %{xxx}o
*/
protected static class ResponseHeaderElement implements AccessLogElement {
private String header;
public ResponseHeaderElement(String header) {
this.header = header;
}
@Override
public void addElement(StringBuilder buf, Date date, Request request,
Response response, long time) {
if (null != response) {
Iterator<String> iter = response.getHeaders(header).iterator();
boolean first = true;
while (iter.hasNext()) {
if (!first) {
buf.append(",");
}
buf.append(iter.next());
}
return ;
}
buf.append("-");
}
}
/**
* write an attribute in the ServletRequest - %{xxx}r
*/
protected static class RequestAttributeElement implements AccessLogElement {
private String header;
public RequestAttributeElement(String header) {
this.header = header;
}
@Override
public void addElement(StringBuilder 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
*/
protected static class SessionAttributeElement implements AccessLogElement {
private String header;
public SessionAttributeElement(String header) {
this.header = header;
}
@Override
public void addElement(StringBuilder 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
*/
protected AccessLogElement[] createLogElements() {
List<AccessLogElement> list = new ArrayList<AccessLogElement>();
boolean replace = false;
StringBuilder buf = new StringBuilder();
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 encounter a closing } - then I ignore the {
*/
if ('{' == ch) {
StringBuilder name = new StringBuilder();
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 StringBuilder();
} else {
buf.append(ch);
}
}
if (buf.length() > 0) {
list.add(new StringElement(buf.toString()));
}
return 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 'o':
return new ResponseHeaderElement(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();
case 'I':
return new ThreadNameElement();
default:
return new StringElement("???" + pattern + "???");
}
}
}