FileDocCategorySizeDatePackage
AccessLogValve.javaAPI DocApache Tomcat 6.0.1443642Fri Jul 20 04:20:36 BST 2007org.apache.catalina.valves

AccessLogValve

public class AccessLogValve extends ValveBase implements org.apache.catalina.Lifecycle

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:

  • %a - Remote IP address
  • %A - Local IP address
  • %b - Bytes sent, excluding HTTP headers, or '-' if no bytes were sent
  • %B - Bytes sent, excluding HTTP headers
  • %h - Remote host name
  • %H - Request protocol
  • %l - Remote logical username from identd (always returns '-')
  • %m - Request method
  • %p - Local port
  • %q - Query string (prepended with a '?' if it exists, otherwise an empty string
  • %r - First line of the request
  • %s - HTTP status code of the response
  • %S - User session ID
  • %t - Date and time, in Common Log Format format
  • %u - Remote user that was authenticated
  • %U - Requested URL path
  • %v - Local server name
  • %D - Time taken to process the request, in millis
  • %T - Time taken to process the request, in seconds

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

  • common - %h %l %u %t "%r" %s %b
  • combined - %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"

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:

  • %{xxx}i for incoming headers
  • %{xxx}c for a specific cookie
  • %{xxx}r xxx is an attribute in the ServletRequest
  • %{xxx}s xxx is an attribute in the HttpSession

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
Remy Maucherat
author
Takayuki Kaneko
version
$Revision: 539787 $ $Date: 2007-01-04 12:17:11 +0900

Fields Summary
private static org.apache.juli.logging.Log
log
private String
dateStamp
The as-of date for the currently open log file, or a zero-length string if there is no open log file.
private String
directory
The directory in which log files are created.
protected static final String
info
The descriptive information about this implementation.
protected org.apache.catalina.util.LifecycleSupport
lifecycle
The lifecycle event support for this component.
protected static final String[]
months
The set of month abbreviations for log messages.
protected boolean
enabled
enabled this component
protected String
pattern
The pattern used to format our access log lines.
protected String
prefix
The prefix that is added to log file filenames.
protected boolean
rotatable
Should we rotate our log file? Default is true (like old behavior)
private boolean
buffered
Buffered logging.
protected org.apache.catalina.util.StringManager
sm
The string manager for this package.
protected boolean
started
Has this component been started yet?
protected String
suffix
The suffix that is added to log file filenames.
protected PrintWriter
writer
The PrintWriter to which we are currently logging, if any.
protected SimpleDateFormat
fileDateFormatter
A date formatter to format a Date into a date in the format "yyyy-MM-dd".
private SimpleDateFormat
dayFormatter
A date formatter to format Dates into a day string in the format "dd".
private SimpleDateFormat
monthFormatter
A date formatter to format a Date into a month string in the format "MM".
private SimpleDateFormat
yearFormatter
A date formatter to format a Date into a year string in the format "yyyy".
private SimpleDateFormat
timeFormatter
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 TimeZone
timezone
The system timezone.
private String
timeZoneNoDST
The time zone offset relative to GMT in text form when daylight saving is not in operation.
private String
timeZoneDST
The time zone offset relative to GMT in text form when daylight saving is in operation.
protected File
currentLogFile
The current log file we are writing to. Helpful when checkExists is true.
private Date
currentDate
The system time when we last updated the Date that this valve uses for log lines.
private long
currentMillis
private boolean
resolveHosts
Resolve hosts.
private long
rotationLastChecked
Instant when the log daily rotation was last checked.
private boolean
checkExists
Do we check for log file existence? Helpful if an external agent renames the log file so we can automagically recreate it.
protected String
condition
Are we doing conditional logging. default false.
protected String
fileDateFormat
Date format to place in log file name. Use at your own risk!
protected AccessLogElement[]
logElements
Array of AccessLogElement, they will be used to make log message.
Constructors Summary
Methods Summary
public voidaddLifecycleListener(org.apache.catalina.LifecycleListener listener)
Add a lifecycle event listener to this component.

param
listener The listener to add

        lifecycle.addLifecycleListener(listener);
    
public voidbackgroundProcess()
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.

        if (started && getEnabled() && writer != null && buffered) {
            writer.flush();
        }
    
private java.lang.StringcalculateTimeZoneOffset(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();
    
private synchronized voidclose()
Close the currently open log file (if any)

        if (writer == null) {
            return;
        }
        writer.flush();
        writer.close();
        writer = null;
        dateStamp = "";
        currentLogFile = null;
    
private org.apache.catalina.valves.AccessLogValve$AccessLogElementcreateAccessLogElement(java.lang.String header, char pattern)
create an AccessLogElement implementation which needs header string

        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("???");
        }
    
private org.apache.catalina.valves.AccessLogValve$AccessLogElementcreateAccessLogElement(char pattern)
create an AccessLogElement implementation

        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 + "???");
        }
    
protected org.apache.catalina.valves.AccessLogValve$AccessLogElement[]createLogElements()
parse pattern string and create the array of AccessLogElement

        List<AccessLogElement> list = new ArrayList<AccessLogElement>();
        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]);
    
public org.apache.catalina.LifecycleListener[]findLifecycleListeners()
Get the lifecycle listeners associated with this lifecycle. If this Lifecycle has no listeners registered, a zero-length array is returned.

        return lifecycle.findLifecycleListeners();
    
public java.lang.StringgetCondition()
Return whether the attribute name to look for when performing conditional loggging. If null, every request is logged.

        return condition;
    
private java.util.DategetDate()
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

        // 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;
    
public java.lang.StringgetDirectory()
Return the directory in which we create log files.

        return (directory);
    
public booleangetEnabled()

return
Returns the enabled.


    // ------------------------------------------------------------- Properties

             
       
        return enabled;
    
public java.lang.StringgetFileDateFormat()
Return the date format date based log rotation.

        return fileDateFormat;
    
public java.lang.StringgetInfo()
Return descriptive information about this implementation.

        return (info);
    
public java.lang.StringgetPattern()
Return the format pattern.

        return (this.pattern);
    
public java.lang.StringgetPrefix()
Return the log file prefix.

        return (prefix);
    
public java.lang.StringgetSuffix()
Return the log file suffix.

        return (suffix);
    
private java.lang.StringgetTimeZone(java.util.Date date)

        if (timezone.inDaylightTime(date)) {
            return timeZoneDST;
        } else {
            return timeZoneNoDST;
        }
    
public voidinvoke(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response)
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


        if (started && getEnabled()) {                
            // 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 (logElements == null || 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());
        } else
            getNext().invoke(request, response);       
    
public booleanisBuffered()
Is the logging buffered

        return buffered;
    
public booleanisCheckExists()
Check for file existence before logging.


        return checkExists;

    
public booleanisResolveHosts()
Get the value of the resolve hosts flag.

        return resolveHosts;
    
public booleanisRotatable()
Should we rotate the logs

        return rotatable;
    
public voidlog(java.lang.String message)
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

        if (rotatable) {
            // Only do a logfile switch check once a second, max.
            long systime = System.currentTimeMillis();
            if ((systime - rotationLastChecked) > 1000) {

                // We need a new currentDate
                currentDate = new Date(systime);
                rotationLastChecked = systime;

                // Check for a change of date
                String tsDate = fileDateFormatter.format(currentDate);

                // If the date has changed, switch log files
                if (!dateStamp.equals(tsDate)) {
                    synchronized (this) {
                        if (!dateStamp.equals(tsDate)) {
                            close();
                            dateStamp = tsDate;
                            open();
                        }
                    }
                }
            }
        }
        
        /* In case something external rotated the file instead */
        if (checkExists) {
            synchronized (this) {
                if (currentLogFile != null && !currentLogFile.exists()) {
                    try {
                        close();
                    } catch (Throwable e) {
                        log.info("at least this wasn't swallowed", e);
                    }

                    /* Make sure date is correct */
                    currentDate = new Date(System.currentTimeMillis());
                    dateStamp = fileDateFormatter.format(currentDate);

                    open();
                }
            }
        }

        // Log this message
        if (writer != null) {
            writer.println(message);
            if (!buffered) {
                writer.flush();
            }
        }

    
private java.lang.Stringlookup(java.lang.String month)
Return the month abbreviation for the specified month, which must be a two-digit String.

param
month Month number ("01" .. "12").

        int index;
        try {
            index = Integer.parseInt(month) - 1;
        } catch (Throwable t) {
            index = 0;  // Can not happen, in theory
        }
        return (months[index]);
    
protected synchronized voidopen()
Open the new log file for the date specified by dateStamp.

        // 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);
            
            currentLogFile = new File(pathname);
        } catch (IOException e) {
            writer = null;
            currentLogFile = null;
        }
    
public voidremoveLifecycleListener(org.apache.catalina.LifecycleListener listener)
Remove a lifecycle event listener from this component.

param
listener The listener to add

        lifecycle.removeLifecycleListener(listener);
    
public synchronized booleanrotate(java.lang.String newFileName)
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


        if (currentLogFile != null) {
            File holder = currentLogFile;
            close();
            try {
                holder.renameTo(new File(newFileName));
            } catch (Throwable e) {
                log.error("rotate failed", e);
            }

            /* Make sure date is correct */
            currentDate = new Date(System.currentTimeMillis());
            dateStamp = fileDateFormatter.format(currentDate);

            open();
            return true;
        } else {
            return false;
        }

    
public voidsetBuffered(boolean buffered)
Set the value if the logging should be buffered

param
buffered true if buffered.

        this.buffered = buffered;
    
public voidsetCheckExists(boolean checkExists)
Set whether to check for log file existence before logging.

param
checkExists true meaning to check for file existence.


        this.checkExists = checkExists;

    
public voidsetCondition(java.lang.String 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

        this.condition = condition;
    
public voidsetDirectory(java.lang.String directory)
Set the directory in which we create log files.

param
directory The new log file directory

        this.directory = directory;
    
public voidsetEnabled(boolean enabled)

param
enabled The enabled to set.

        this.enabled = enabled;
    
public voidsetFileDateFormat(java.lang.String fileDateFormat)
Set the date format date based log rotation.

        this.fileDateFormat =  fileDateFormat;
    
public voidsetPattern(java.lang.String pattern)
Set the format pattern, first translating any recognized alias.

param
pattern The new 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();
    
public voidsetPrefix(java.lang.String prefix)
Set the log file prefix.

param
prefix The new log file prefix

        this.prefix = prefix;
    
public voidsetResolveHosts(boolean resolveHosts)
Set the resolve hosts flag.

param
resolveHosts The new resolve hosts value

        this.resolveHosts = resolveHosts;
    
public voidsetRotatable(boolean rotatable)
Set the value is we should we rotate the logs

param
rotatable true is we should rotate.

        this.rotatable = rotatable;
    
public voidsetSuffix(java.lang.String suffix)
Set the log file suffix.

param
suffix The new log file suffix

        this.suffix = suffix;
    
public voidstart()
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


        // 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";
        fileDateFormatter = new SimpleDateFormat(fileDateFormat);
        fileDateFormatter.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 = fileDateFormatter.format(currentDate);
        open();
    
public voidstop()
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


        // Validate and update our current component state
        if (!started)
            throw new LifecycleException(sm
                    .getString("accessLogValve.notStarted"));
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;
        close();