0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.valves;
0019:
0020: import java.io.BufferedWriter;
0021: import java.io.File;
0022: import java.io.FileWriter;
0023: import java.io.IOException;
0024: import java.io.PrintWriter;
0025: import java.net.InetAddress;
0026: import java.text.SimpleDateFormat;
0027: import java.util.ArrayList;
0028: import java.util.Calendar;
0029: import java.util.Date;
0030: import java.util.List;
0031: import java.util.TimeZone;
0032:
0033: import javax.servlet.ServletException;
0034: import javax.servlet.http.Cookie;
0035: import javax.servlet.http.HttpSession;
0036:
0037: import org.apache.catalina.Lifecycle;
0038: import org.apache.catalina.LifecycleException;
0039: import org.apache.catalina.LifecycleListener;
0040: import org.apache.catalina.connector.Request;
0041: import org.apache.catalina.connector.Response;
0042: import org.apache.catalina.util.LifecycleSupport;
0043: import org.apache.catalina.util.StringManager;
0044: import org.apache.juli.logging.Log;
0045: import org.apache.juli.logging.LogFactory;
0046:
0047: /**
0048: * <p>Implementation of the <b>Valve</b> interface that generates a web server
0049: * access log with the detailed line contents matching a configurable pattern.
0050: * The syntax of the available patterns is similar to that supported by the
0051: * Apache <code>mod_log_config</code> module. As an additional feature,
0052: * automatic rollover of log files when the date changes is also supported.</p>
0053: *
0054: * <p>Patterns for the logged message may include constant text or any of the
0055: * following replacement strings, for which the corresponding information
0056: * from the specified Response is substituted:</p>
0057: * <ul>
0058: * <li><b>%a</b> - Remote IP address
0059: * <li><b>%A</b> - Local IP address
0060: * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
0061: * were sent
0062: * <li><b>%B</b> - Bytes sent, excluding HTTP headers
0063: * <li><b>%h</b> - Remote host name
0064: * <li><b>%H</b> - Request protocol
0065: * <li><b>%l</b> - Remote logical username from identd (always returns '-')
0066: * <li><b>%m</b> - Request method
0067: * <li><b>%p</b> - Local port
0068: * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
0069: * an empty string
0070: * <li><b>%r</b> - First line of the request
0071: * <li><b>%s</b> - HTTP status code of the response
0072: * <li><b>%S</b> - User session ID
0073: * <li><b>%t</b> - Date and time, in Common Log Format format
0074: * <li><b>%u</b> - Remote user that was authenticated
0075: * <li><b>%U</b> - Requested URL path
0076: * <li><b>%v</b> - Local server name
0077: * <li><b>%D</b> - Time taken to process the request, in millis
0078: * <li><b>%T</b> - Time taken to process the request, in seconds
0079: * </ul>
0080: * <p>In addition, the caller can specify one of the following aliases for
0081: * commonly utilized patterns:</p>
0082: * <ul>
0083: * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
0084: * <li><b>combined</b> -
0085: * <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
0086: * </ul>
0087: *
0088: * <p>
0089: * There is also support to write information from the cookie, incoming
0090: * header, the Session or something else in the ServletRequest.<br>
0091: * It is modeled after the apache syntax:
0092: * <ul>
0093: * <li><code>%{xxx}i</code> for incoming headers
0094: * <li><code>%{xxx}c</code> for a specific cookie
0095: * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
0096: * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
0097: * </ul>
0098: * </p>
0099: *
0100: * <p>
0101: * Conditional logging is also supported. This can be done with the
0102: * <code>condition</code> property.
0103: * If the value returned from ServletRequest.getAttribute(condition)
0104: * yields a non-null value. The logging will be skipped.
0105: * </p>
0106: *
0107: * @author Craig R. McClanahan
0108: * @author Jason Brittain
0109: * @author Remy Maucherat
0110: * @author Takayuki Kaneko
0111: * @version $Revision: 539787 $ $Date: 2007-01-04 12:17:11 +0900
0112: */
0113:
0114: public class AccessLogValve extends ValveBase implements Lifecycle {
0115:
0116: private static Log log = LogFactory.getLog(AccessLogValve.class);
0117:
0118: // ----------------------------------------------------- Instance Variables
0119:
0120: /**
0121: * The as-of date for the currently open log file, or a zero-length
0122: * string if there is no open log file.
0123: */
0124: private String dateStamp = "";
0125:
0126: /**
0127: * The directory in which log files are created.
0128: */
0129: private String directory = "logs";
0130:
0131: /**
0132: * The descriptive information about this implementation.
0133: */
0134: protected static final String info = "org.apache.catalina.valves.AccessLogValve/2.0";
0135:
0136: /**
0137: * The lifecycle event support for this component.
0138: */
0139: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0140:
0141: /**
0142: * The set of month abbreviations for log messages.
0143: */
0144: protected static final String months[] = { "Jan", "Feb", "Mar",
0145: "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
0146: "Dec" };
0147:
0148: /**
0149: * enabled this component
0150: */
0151: protected boolean enabled = true;
0152:
0153: /**
0154: * The pattern used to format our access log lines.
0155: */
0156: protected String pattern = null;
0157:
0158: /**
0159: * The prefix that is added to log file filenames.
0160: */
0161: protected String prefix = "access_log.";
0162:
0163: /**
0164: * Should we rotate our log file? Default is true (like old behavior)
0165: */
0166: protected boolean rotatable = true;
0167:
0168: /**
0169: * Buffered logging.
0170: */
0171: private boolean buffered = true;
0172:
0173: /**
0174: * The string manager for this package.
0175: */
0176: protected StringManager sm = StringManager
0177: .getManager(Constants.Package);
0178:
0179: /**
0180: * Has this component been started yet?
0181: */
0182: protected boolean started = false;
0183:
0184: /**
0185: * The suffix that is added to log file filenames.
0186: */
0187: protected String suffix = "";
0188:
0189: /**
0190: * The PrintWriter to which we are currently logging, if any.
0191: */
0192: protected PrintWriter writer = null;
0193:
0194: /**
0195: * A date formatter to format a Date into a date in the format
0196: * "yyyy-MM-dd".
0197: */
0198: protected SimpleDateFormat fileDateFormatter = null;
0199:
0200: /**
0201: * A date formatter to format Dates into a day string in the format
0202: * "dd".
0203: */
0204: private SimpleDateFormat dayFormatter = null;
0205:
0206: /**
0207: * A date formatter to format a Date into a month string in the format
0208: * "MM".
0209: */
0210: private SimpleDateFormat monthFormatter = null;
0211:
0212: /**
0213: * A date formatter to format a Date into a year string in the format
0214: * "yyyy".
0215: */
0216: private SimpleDateFormat yearFormatter = null;
0217:
0218: /**
0219: * A date formatter to format a Date into a time in the format
0220: * "kk:mm:ss" (kk is a 24-hour representation of the hour).
0221: */
0222: private SimpleDateFormat timeFormatter = null;
0223:
0224: /**
0225: * The system timezone.
0226: */
0227: private TimeZone timezone = null;
0228:
0229: /**
0230: * The time zone offset relative to GMT in text form when daylight saving
0231: * is not in operation.
0232: */
0233: private String timeZoneNoDST = null;
0234:
0235: /**
0236: * The time zone offset relative to GMT in text form when daylight saving
0237: * is in operation.
0238: */
0239: private String timeZoneDST = null;
0240:
0241: /**
0242: * The current log file we are writing to. Helpful when checkExists
0243: * is true.
0244: */
0245: protected File currentLogFile = null;
0246:
0247: /**
0248: * The system time when we last updated the Date that this valve
0249: * uses for log lines.
0250: */
0251: private Date currentDate = null;
0252:
0253: private long currentMillis = 0;
0254:
0255: /**
0256: * Resolve hosts.
0257: */
0258: private boolean resolveHosts = false;
0259:
0260: /**
0261: * Instant when the log daily rotation was last checked.
0262: */
0263: private long rotationLastChecked = 0L;
0264:
0265: /**
0266: * Do we check for log file existence? Helpful if an external
0267: * agent renames the log file so we can automagically recreate it.
0268: */
0269: private boolean checkExists = false;
0270:
0271: /**
0272: * Are we doing conditional logging. default false.
0273: */
0274: protected String condition = null;
0275:
0276: /**
0277: * Date format to place in log file name. Use at your own risk!
0278: */
0279: protected String fileDateFormat = null;
0280:
0281: /**
0282: * Array of AccessLogElement, they will be used to make log message.
0283: */
0284: protected AccessLogElement[] logElements = null;
0285:
0286: // ------------------------------------------------------------- Properties
0287:
0288: /**
0289: * @return Returns the enabled.
0290: */
0291: public boolean getEnabled() {
0292: return enabled;
0293: }
0294:
0295: /**
0296: * @param enabled
0297: * The enabled to set.
0298: */
0299: public void setEnabled(boolean enabled) {
0300: this .enabled = enabled;
0301: }
0302:
0303: /**
0304: * Return the directory in which we create log files.
0305: */
0306: public String getDirectory() {
0307: return (directory);
0308: }
0309:
0310: /**
0311: * Set the directory in which we create log files.
0312: *
0313: * @param directory The new log file directory
0314: */
0315: public void setDirectory(String directory) {
0316: this .directory = directory;
0317: }
0318:
0319: /**
0320: * Return descriptive information about this implementation.
0321: */
0322: public String getInfo() {
0323: return (info);
0324: }
0325:
0326: /**
0327: * Return the format pattern.
0328: */
0329: public String getPattern() {
0330: return (this .pattern);
0331: }
0332:
0333: /**
0334: * Set the format pattern, first translating any recognized alias.
0335: *
0336: * @param pattern The new pattern
0337: */
0338: public void setPattern(String pattern) {
0339: if (pattern == null)
0340: pattern = "";
0341: if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
0342: pattern = Constants.AccessLog.COMMON_PATTERN;
0343: if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
0344: pattern = Constants.AccessLog.COMBINED_PATTERN;
0345: this .pattern = pattern;
0346: logElements = createLogElements();
0347: }
0348:
0349: /**
0350: * Check for file existence before logging.
0351: */
0352: public boolean isCheckExists() {
0353:
0354: return checkExists;
0355:
0356: }
0357:
0358: /**
0359: * Set whether to check for log file existence before logging.
0360: *
0361: * @param checkExists true meaning to check for file existence.
0362: */
0363: public void setCheckExists(boolean checkExists) {
0364:
0365: this .checkExists = checkExists;
0366:
0367: }
0368:
0369: /**
0370: * Return the log file prefix.
0371: */
0372: public String getPrefix() {
0373: return (prefix);
0374: }
0375:
0376: /**
0377: * Set the log file prefix.
0378: *
0379: * @param prefix The new log file prefix
0380: */
0381: public void setPrefix(String prefix) {
0382: this .prefix = prefix;
0383: }
0384:
0385: /**
0386: * Should we rotate the logs
0387: */
0388: public boolean isRotatable() {
0389: return rotatable;
0390: }
0391:
0392: /**
0393: * Set the value is we should we rotate the logs
0394: *
0395: * @param rotatable true is we should rotate.
0396: */
0397: public void setRotatable(boolean rotatable) {
0398: this .rotatable = rotatable;
0399: }
0400:
0401: /**
0402: * Is the logging buffered
0403: */
0404: public boolean isBuffered() {
0405: return buffered;
0406: }
0407:
0408: /**
0409: * Set the value if the logging should be buffered
0410: *
0411: * @param buffered true if buffered.
0412: */
0413: public void setBuffered(boolean buffered) {
0414: this .buffered = buffered;
0415: }
0416:
0417: /**
0418: * Return the log file suffix.
0419: */
0420: public String getSuffix() {
0421: return (suffix);
0422: }
0423:
0424: /**
0425: * Set the log file suffix.
0426: *
0427: * @param suffix The new log file suffix
0428: */
0429: public void setSuffix(String suffix) {
0430: this .suffix = suffix;
0431: }
0432:
0433: /**
0434: * Set the resolve hosts flag.
0435: *
0436: * @param resolveHosts The new resolve hosts value
0437: */
0438: public void setResolveHosts(boolean resolveHosts) {
0439: this .resolveHosts = resolveHosts;
0440: }
0441:
0442: /**
0443: * Get the value of the resolve hosts flag.
0444: */
0445: public boolean isResolveHosts() {
0446: return resolveHosts;
0447: }
0448:
0449: /**
0450: * Return whether the attribute name to look for when
0451: * performing conditional loggging. If null, every
0452: * request is logged.
0453: */
0454: public String getCondition() {
0455: return condition;
0456: }
0457:
0458: /**
0459: * Set the ServletRequest.attribute to look for to perform
0460: * conditional logging. Set to null to log everything.
0461: *
0462: * @param condition Set to null to log everything
0463: */
0464: public void setCondition(String condition) {
0465: this .condition = condition;
0466: }
0467:
0468: /**
0469: * Return the date format date based log rotation.
0470: */
0471: public String getFileDateFormat() {
0472: return fileDateFormat;
0473: }
0474:
0475: /**
0476: * Set the date format date based log rotation.
0477: */
0478: public void setFileDateFormat(String fileDateFormat) {
0479: this .fileDateFormat = fileDateFormat;
0480: }
0481:
0482: // --------------------------------------------------------- Public Methods
0483:
0484: /**
0485: * Execute a periodic task, such as reloading, etc. This method will be
0486: * invoked inside the classloading context of this container. Unexpected
0487: * throwables will be caught and logged.
0488: */
0489: public void backgroundProcess() {
0490: if (started && getEnabled() && writer != null && buffered) {
0491: writer.flush();
0492: }
0493: }
0494:
0495: /**
0496: * Log a message summarizing the specified request and response, according
0497: * to the format specified by the <code>pattern</code> property.
0498: *
0499: * @param request Request being processed
0500: * @param response Response being processed
0501: *
0502: * @exception IOException if an input/output error has occurred
0503: * @exception ServletException if a servlet error has occurred
0504: */
0505: public void invoke(Request request, Response response)
0506: throws IOException, ServletException {
0507:
0508: if (started && getEnabled()) {
0509: // Pass this request on to the next valve in our pipeline
0510: long t1 = System.currentTimeMillis();
0511:
0512: getNext().invoke(request, response);
0513:
0514: long t2 = System.currentTimeMillis();
0515: long time = t2 - t1;
0516:
0517: if (logElements == null
0518: || condition != null
0519: && null != request.getRequest().getAttribute(
0520: condition)) {
0521: return;
0522: }
0523:
0524: Date date = getDate();
0525: StringBuffer result = new StringBuffer();
0526:
0527: for (int i = 0; i < logElements.length; i++) {
0528: logElements[i].addElement(result, date, request,
0529: response, time);
0530: }
0531:
0532: log(result.toString());
0533: } else
0534: getNext().invoke(request, response);
0535: }
0536:
0537: /**
0538: * Rename the existing log file to something else. Then open the
0539: * old log file name up once again. Intended to be called by a JMX
0540: * agent.
0541: *
0542: *
0543: * @param newFileName The file name to move the log file entry to
0544: * @return true if a file was rotated with no error
0545: */
0546: public synchronized boolean rotate(String newFileName) {
0547:
0548: if (currentLogFile != null) {
0549: File holder = currentLogFile;
0550: close();
0551: try {
0552: holder.renameTo(new File(newFileName));
0553: } catch (Throwable e) {
0554: log.error("rotate failed", e);
0555: }
0556:
0557: /* Make sure date is correct */
0558: currentDate = new Date(System.currentTimeMillis());
0559: dateStamp = fileDateFormatter.format(currentDate);
0560:
0561: open();
0562: return true;
0563: } else {
0564: return false;
0565: }
0566:
0567: }
0568:
0569: // -------------------------------------------------------- Private Methods
0570:
0571: /**
0572: * Close the currently open log file (if any)
0573: */
0574: private synchronized void close() {
0575: if (writer == null) {
0576: return;
0577: }
0578: writer.flush();
0579: writer.close();
0580: writer = null;
0581: dateStamp = "";
0582: currentLogFile = null;
0583: }
0584:
0585: /**
0586: * Log the specified message to the log file, switching files if the date
0587: * has changed since the previous log call.
0588: *
0589: * @param message Message to be logged
0590: */
0591: public void log(String message) {
0592: if (rotatable) {
0593: // Only do a logfile switch check once a second, max.
0594: long systime = System.currentTimeMillis();
0595: if ((systime - rotationLastChecked) > 1000) {
0596:
0597: // We need a new currentDate
0598: currentDate = new Date(systime);
0599: rotationLastChecked = systime;
0600:
0601: // Check for a change of date
0602: String tsDate = fileDateFormatter.format(currentDate);
0603:
0604: // If the date has changed, switch log files
0605: if (!dateStamp.equals(tsDate)) {
0606: synchronized (this ) {
0607: if (!dateStamp.equals(tsDate)) {
0608: close();
0609: dateStamp = tsDate;
0610: open();
0611: }
0612: }
0613: }
0614: }
0615: }
0616:
0617: /* In case something external rotated the file instead */
0618: if (checkExists) {
0619: synchronized (this ) {
0620: if (currentLogFile != null && !currentLogFile.exists()) {
0621: try {
0622: close();
0623: } catch (Throwable e) {
0624: log.info("at least this wasn't swallowed", e);
0625: }
0626:
0627: /* Make sure date is correct */
0628: currentDate = new Date(System.currentTimeMillis());
0629: dateStamp = fileDateFormatter.format(currentDate);
0630:
0631: open();
0632: }
0633: }
0634: }
0635:
0636: // Log this message
0637: if (writer != null) {
0638: writer.println(message);
0639: if (!buffered) {
0640: writer.flush();
0641: }
0642: }
0643:
0644: }
0645:
0646: /**
0647: * Return the month abbreviation for the specified month, which must
0648: * be a two-digit String.
0649: *
0650: * @param month Month number ("01" .. "12").
0651: */
0652: private String lookup(String month) {
0653: int index;
0654: try {
0655: index = Integer.parseInt(month) - 1;
0656: } catch (Throwable t) {
0657: index = 0; // Can not happen, in theory
0658: }
0659: return (months[index]);
0660: }
0661:
0662: /**
0663: * Open the new log file for the date specified by <code>dateStamp</code>.
0664: */
0665: protected synchronized void open() {
0666: // Create the directory if necessary
0667: File dir = new File(directory);
0668: if (!dir.isAbsolute())
0669: dir = new File(System.getProperty("catalina.base"),
0670: directory);
0671: dir.mkdirs();
0672:
0673: // Open the current log file
0674: try {
0675: String pathname;
0676: // If no rotate - no need for dateStamp in fileName
0677: if (rotatable) {
0678: pathname = dir.getAbsolutePath() + File.separator
0679: + prefix + dateStamp + suffix;
0680: } else {
0681: pathname = dir.getAbsolutePath() + File.separator
0682: + prefix + suffix;
0683: }
0684: writer = new PrintWriter(new BufferedWriter(new FileWriter(
0685: pathname, true), 128000), false);
0686:
0687: currentLogFile = new File(pathname);
0688: } catch (IOException e) {
0689: writer = null;
0690: currentLogFile = null;
0691: }
0692: }
0693:
0694: /**
0695: * This method returns a Date object that is accurate to within one second.
0696: * If a thread calls this method to get a Date and it's been less than 1
0697: * second since a new Date was created, this method simply gives out the
0698: * same Date again so that the system doesn't spend time creating Date
0699: * objects unnecessarily.
0700: *
0701: * @return Date
0702: */
0703: private Date getDate() {
0704: // Only create a new Date once per second, max.
0705: long systime = System.currentTimeMillis();
0706: if ((systime - currentMillis) > 1000) {
0707: synchronized (this ) {
0708: if ((systime - currentMillis) > 1000) {
0709: currentDate = new Date(systime);
0710: currentMillis = systime;
0711: }
0712: }
0713: }
0714: return currentDate;
0715: }
0716:
0717: private String getTimeZone(Date date) {
0718: if (timezone.inDaylightTime(date)) {
0719: return timeZoneDST;
0720: } else {
0721: return timeZoneNoDST;
0722: }
0723: }
0724:
0725: private String calculateTimeZoneOffset(long offset) {
0726: StringBuffer tz = new StringBuffer();
0727: if ((offset < 0)) {
0728: tz.append("-");
0729: offset = -offset;
0730: } else {
0731: tz.append("+");
0732: }
0733:
0734: long hourOffset = offset / (1000 * 60 * 60);
0735: long minuteOffset = (offset / (1000 * 60)) % 60;
0736:
0737: if (hourOffset < 10)
0738: tz.append("0");
0739: tz.append(hourOffset);
0740:
0741: if (minuteOffset < 10)
0742: tz.append("0");
0743: tz.append(minuteOffset);
0744:
0745: return tz.toString();
0746: }
0747:
0748: // ------------------------------------------------------ Lifecycle Methods
0749:
0750: /**
0751: * Add a lifecycle event listener to this component.
0752: *
0753: * @param listener The listener to add
0754: */
0755: public void addLifecycleListener(LifecycleListener listener) {
0756: lifecycle.addLifecycleListener(listener);
0757: }
0758:
0759: /**
0760: * Get the lifecycle listeners associated with this lifecycle. If this
0761: * Lifecycle has no listeners registered, a zero-length array is returned.
0762: */
0763: public LifecycleListener[] findLifecycleListeners() {
0764: return lifecycle.findLifecycleListeners();
0765: }
0766:
0767: /**
0768: * Remove a lifecycle event listener from this component.
0769: *
0770: * @param listener The listener to add
0771: */
0772: public void removeLifecycleListener(LifecycleListener listener) {
0773: lifecycle.removeLifecycleListener(listener);
0774: }
0775:
0776: /**
0777: * Prepare for the beginning of active use of the public methods of this
0778: * component. This method should be called after <code>configure()</code>,
0779: * and before any of the public methods of the component are utilized.
0780: *
0781: * @exception LifecycleException if this component detects a fatal error
0782: * that prevents this component from being used
0783: */
0784: public void start() throws LifecycleException {
0785:
0786: // Validate and update our current component state
0787: if (started)
0788: throw new LifecycleException(sm
0789: .getString("accessLogValve.alreadyStarted"));
0790: lifecycle.fireLifecycleEvent(START_EVENT, null);
0791: started = true;
0792:
0793: // Initialize the timeZone, Date formatters, and currentDate
0794: timezone = TimeZone.getDefault();
0795: timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
0796: Calendar calendar = Calendar.getInstance(timezone);
0797: int offset = calendar.get(Calendar.DST_OFFSET);
0798: timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset()
0799: + offset);
0800:
0801: if (fileDateFormat == null || fileDateFormat.length() == 0)
0802: fileDateFormat = "yyyy-MM-dd";
0803: fileDateFormatter = new SimpleDateFormat(fileDateFormat);
0804: fileDateFormatter.setTimeZone(timezone);
0805: dayFormatter = new SimpleDateFormat("dd");
0806: dayFormatter.setTimeZone(timezone);
0807: monthFormatter = new SimpleDateFormat("MM");
0808: monthFormatter.setTimeZone(timezone);
0809: yearFormatter = new SimpleDateFormat("yyyy");
0810: yearFormatter.setTimeZone(timezone);
0811: timeFormatter = new SimpleDateFormat("HH:mm:ss");
0812: timeFormatter.setTimeZone(timezone);
0813: currentDate = new Date();
0814: dateStamp = fileDateFormatter.format(currentDate);
0815: open();
0816: }
0817:
0818: /**
0819: * Gracefully terminate the active use of the public methods of this
0820: * component. This method should be the last one called on a given
0821: * instance of this component.
0822: *
0823: * @exception LifecycleException if this component detects a fatal error
0824: * that needs to be reported
0825: */
0826: public void stop() throws LifecycleException {
0827:
0828: // Validate and update our current component state
0829: if (!started)
0830: throw new LifecycleException(sm
0831: .getString("accessLogValve.notStarted"));
0832: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
0833: started = false;
0834: close();
0835: }
0836:
0837: /**
0838: * AccessLogElement writes the partial message into the buffer.
0839: */
0840: protected interface AccessLogElement {
0841: public void addElement(StringBuffer buf, Date date,
0842: Request request, Response response, long time);
0843:
0844: }
0845:
0846: /**
0847: * write local IP address - %A
0848: */
0849: protected class LocalAddrElement implements AccessLogElement {
0850:
0851: private String value = null;
0852:
0853: public void addElement(StringBuffer buf, Date date,
0854: Request request, Response response, long time) {
0855: if (value == null) {
0856: synchronized (this ) {
0857: try {
0858: value = InetAddress.getLocalHost()
0859: .getHostAddress();
0860: } catch (Throwable e) {
0861: value = "127.0.0.1";
0862: }
0863: }
0864: }
0865: buf.append(value);
0866: }
0867: }
0868:
0869: /**
0870: * write remote IP address - %a
0871: */
0872: protected class RemoteAddrElement implements AccessLogElement {
0873: public void addElement(StringBuffer buf, Date date,
0874: Request request, Response response, long time) {
0875: buf.append(request.getRemoteAddr());
0876: }
0877: }
0878:
0879: /**
0880: * write remote host name - %h
0881: */
0882: protected class HostElement implements AccessLogElement {
0883: public void addElement(StringBuffer buf, Date date,
0884: Request request, Response response, long time) {
0885: buf.append(request.getRemoteHost());
0886: }
0887: }
0888:
0889: /**
0890: * write remote logical username from identd (always returns '-') - %l
0891: */
0892: protected class LogicalUserNameElement implements AccessLogElement {
0893: public void addElement(StringBuffer buf, Date date,
0894: Request request, Response response, long time) {
0895: buf.append('-');
0896: }
0897: }
0898:
0899: /**
0900: * write request protocol - %H
0901: */
0902: protected class ProtocolElement implements AccessLogElement {
0903: public void addElement(StringBuffer buf, Date date,
0904: Request request, Response response, long time) {
0905: buf.append(request.getProtocol());
0906: }
0907: }
0908:
0909: /**
0910: * write remote user that was authenticated (if any), else '-' - %u
0911: */
0912: protected class UserElement implements AccessLogElement {
0913: public void addElement(StringBuffer buf, Date date,
0914: Request request, Response response, long time) {
0915: if (request != null) {
0916: String value = request.getRemoteUser();
0917: if (value != null) {
0918: buf.append(value);
0919: } else {
0920: buf.append('-');
0921: }
0922: } else {
0923: buf.append('-');
0924: }
0925: }
0926: }
0927:
0928: /**
0929: * write date and time, in Common Log Format - %t
0930: */
0931: protected class DateAndTimeElement implements AccessLogElement {
0932: private Date currentDate = new Date(0);
0933:
0934: private String currentDateString = null;
0935:
0936: public void addElement(StringBuffer buf, Date date,
0937: Request request, Response response, long time) {
0938: if (currentDate != date) {
0939: synchronized (this ) {
0940: if (currentDate != date) {
0941: StringBuffer current = new StringBuffer(32);
0942: current.append('[');
0943: current.append(dayFormatter.format(date)); // Day
0944: current.append('/');
0945: current.append(lookup(monthFormatter
0946: .format(date))); // Month
0947: current.append('/');
0948: current.append(yearFormatter.format(date)); // Year
0949: current.append(':');
0950: current.append(timeFormatter.format(date)); // Time
0951: current.append(' ');
0952: current.append(getTimeZone(date)); // Timezone
0953: current.append(']');
0954: currentDateString = current.toString();
0955: currentDate = date;
0956: }
0957: }
0958: }
0959: buf.append(currentDateString);
0960: }
0961: }
0962:
0963: /**
0964: * write first line of the request (method and request URI) - %r
0965: */
0966: protected class RequestElement implements AccessLogElement {
0967: public void addElement(StringBuffer buf, Date date,
0968: Request request, Response response, long time) {
0969: if (request != null) {
0970: buf.append(request.getMethod());
0971: buf.append(' ');
0972: buf.append(request.getRequestURI());
0973: if (request.getQueryString() != null) {
0974: buf.append('?');
0975: buf.append(request.getQueryString());
0976: }
0977: buf.append(' ');
0978: buf.append(request.getProtocol());
0979: } else {
0980: buf.append("- - ");
0981: }
0982: }
0983: }
0984:
0985: /**
0986: * write HTTP status code of the response - %s
0987: */
0988: protected class HttpStatusCodeElement implements AccessLogElement {
0989: public void addElement(StringBuffer buf, Date date,
0990: Request request, Response response, long time) {
0991: if (response != null) {
0992: buf.append(response.getStatus());
0993: } else {
0994: buf.append('-');
0995: }
0996: }
0997: }
0998:
0999: /**
1000: * write local port on which this request was received - %p
1001: */
1002: protected class LocalPortElement implements AccessLogElement {
1003: public void addElement(StringBuffer buf, Date date,
1004: Request request, Response response, long time) {
1005: buf.append(request.getServerPort());
1006: }
1007: }
1008:
1009: /**
1010: * write bytes sent, excluding HTTP headers - %b, %B
1011: */
1012: protected class ByteSentElement implements AccessLogElement {
1013: private boolean conversion;
1014:
1015: /**
1016: * if conversion is true, write '-' instead of 0 - %b
1017: */
1018: public ByteSentElement(boolean conversion) {
1019: this .conversion = conversion;
1020: }
1021:
1022: public void addElement(StringBuffer buf, Date date,
1023: Request request, Response response, long time) {
1024: int length = response.getContentCount();
1025: if (length <= 0 && conversion) {
1026: buf.append('-');
1027: } else {
1028: buf.append(length);
1029: }
1030: }
1031: }
1032:
1033: /**
1034: * write request method (GET, POST, etc.) - %m
1035: */
1036: protected class MethodElement implements AccessLogElement {
1037: public void addElement(StringBuffer buf, Date date,
1038: Request request, Response response, long time) {
1039: if (request != null) {
1040: buf.append(request.getMethod());
1041: }
1042: }
1043: }
1044:
1045: /**
1046: * write time taken to process the request - %D, %T
1047: */
1048: protected class ElapsedTimeElement implements AccessLogElement {
1049: private boolean millis;
1050:
1051: /**
1052: * if millis is true, write time in millis - %D
1053: * if millis is false, write time in seconds - %T
1054: */
1055: public ElapsedTimeElement(boolean millis) {
1056: this .millis = millis;
1057: }
1058:
1059: public void addElement(StringBuffer buf, Date date,
1060: Request request, Response response, long time) {
1061: if (millis) {
1062: buf.append(time);
1063: } else {
1064: // second
1065: buf.append(time / 1000);
1066: buf.append('.');
1067: int remains = (int) (time % 1000);
1068: buf.append(remains / 100);
1069: remains = remains % 100;
1070: buf.append(remains / 10);
1071: buf.append(remains % 10);
1072: }
1073: }
1074: }
1075:
1076: /**
1077: * write Query string (prepended with a '?' if it exists) - %q
1078: */
1079: protected class QueryElement implements AccessLogElement {
1080: public void addElement(StringBuffer buf, Date date,
1081: Request request, Response response, long time) {
1082: String query = null;
1083: if (request != null)
1084: query = request.getQueryString();
1085: if (query != null) {
1086: buf.append('?');
1087: buf.append(query);
1088: }
1089: }
1090: }
1091:
1092: /**
1093: * write user session ID - %S
1094: */
1095: protected class SessionIdElement implements AccessLogElement {
1096: public void addElement(StringBuffer buf, Date date,
1097: Request request, Response response, long time) {
1098: if (request != null) {
1099: if (request.getSession(false) != null) {
1100: buf.append(request.getSessionInternal(false)
1101: .getIdInternal());
1102: } else {
1103: buf.append('-');
1104: }
1105: } else {
1106: buf.append('-');
1107: }
1108: }
1109: }
1110:
1111: /**
1112: * write requested URL path - %U
1113: */
1114: protected class RequestURIElement implements AccessLogElement {
1115: public void addElement(StringBuffer buf, Date date,
1116: Request request, Response response, long time) {
1117: if (request != null) {
1118: buf.append(request.getRequestURI());
1119: } else {
1120: buf.append('-');
1121: }
1122: }
1123: }
1124:
1125: /**
1126: * write local server name - %v
1127: */
1128: protected class LocalServerNameElement implements AccessLogElement {
1129: public void addElement(StringBuffer buf, Date date,
1130: Request request, Response response, long time) {
1131: buf.append(request.getServerName());
1132: }
1133: }
1134:
1135: /**
1136: * write any string
1137: */
1138: protected class StringElement implements AccessLogElement {
1139: private String str;
1140:
1141: public StringElement(String str) {
1142: this .str = str;
1143: }
1144:
1145: public void addElement(StringBuffer buf, Date date,
1146: Request request, Response response, long time) {
1147: buf.append(str);
1148: }
1149: }
1150:
1151: /**
1152: * write incoming headers - %{xxx}i
1153: */
1154: protected class HeaderElement implements AccessLogElement {
1155: private String header;
1156:
1157: public HeaderElement(String header) {
1158: this .header = header;
1159: }
1160:
1161: public void addElement(StringBuffer buf, Date date,
1162: Request request, Response response, long time) {
1163: buf.append(request.getHeader(header));
1164: }
1165: }
1166:
1167: /**
1168: * write a specific cookie - %{xxx}c
1169: */
1170: protected class CookieElement implements AccessLogElement {
1171: private String header;
1172:
1173: public CookieElement(String header) {
1174: this .header = header;
1175: }
1176:
1177: public void addElement(StringBuffer buf, Date date,
1178: Request request, Response response, long time) {
1179: String value = "-";
1180: Cookie[] c = request.getCookies();
1181: if (c != null) {
1182: for (int i = 0; i < c.length; i++) {
1183: if (header.equals(c[i].getName())) {
1184: value = c[i].getValue();
1185: break;
1186: }
1187: }
1188: }
1189: buf.append(value);
1190: }
1191: }
1192:
1193: /**
1194: * write an attribute in the ServletRequest - %{xxx}r
1195: */
1196: protected class RequestAttributeElement implements AccessLogElement {
1197: private String header;
1198:
1199: public RequestAttributeElement(String header) {
1200: this .header = header;
1201: }
1202:
1203: public void addElement(StringBuffer buf, Date date,
1204: Request request, Response response, long time) {
1205: Object value = null;
1206: if (request != null) {
1207: value = request.getAttribute(header);
1208: } else {
1209: value = "??";
1210: }
1211: if (value != null) {
1212: if (value instanceof String) {
1213: buf.append((String) value);
1214: } else {
1215: buf.append(value.toString());
1216: }
1217: } else {
1218: buf.append('-');
1219: }
1220: }
1221: }
1222:
1223: /**
1224: * write an attribute in the HttpSession - %{xxx}s
1225: */
1226: protected class SessionAttributeElement implements AccessLogElement {
1227: private String header;
1228:
1229: public SessionAttributeElement(String header) {
1230: this .header = header;
1231: }
1232:
1233: public void addElement(StringBuffer buf, Date date,
1234: Request request, Response response, long time) {
1235: Object value = null;
1236: if (null != request) {
1237: HttpSession sess = request.getSession(false);
1238: if (null != sess)
1239: value = sess.getAttribute(header);
1240: } else {
1241: value = "??";
1242: }
1243: if (value != null) {
1244: if (value instanceof String) {
1245: buf.append((String) value);
1246: } else {
1247: buf.append(value.toString());
1248: }
1249: } else {
1250: buf.append('-');
1251: }
1252: }
1253: }
1254:
1255: /**
1256: * parse pattern string and create the array of AccessLogElement
1257: */
1258: protected AccessLogElement[] createLogElements() {
1259: List<AccessLogElement> list = new ArrayList<AccessLogElement>();
1260: boolean replace = false;
1261: StringBuffer buf = new StringBuffer();
1262: for (int i = 0; i < pattern.length(); i++) {
1263: char ch = pattern.charAt(i);
1264: if (replace) {
1265: /*
1266: * For code that processes {, the behavior will be ... if I do
1267: * not enounter a closing } - then I ignore the {
1268: */
1269: if ('{' == ch) {
1270: StringBuffer name = new StringBuffer();
1271: int j = i + 1;
1272: for (; j < pattern.length()
1273: && '}' != pattern.charAt(j); j++) {
1274: name.append(pattern.charAt(j));
1275: }
1276: if (j + 1 < pattern.length()) {
1277: /* the +1 was to account for } which we increment now */
1278: j++;
1279: list.add(createAccessLogElement(
1280: name.toString(), pattern.charAt(j)));
1281: i = j; /* Since we walked more than one character */
1282: } else {
1283: // D'oh - end of string - pretend we never did this
1284: // and do processing the "old way"
1285: list.add(createAccessLogElement(ch));
1286: }
1287: } else {
1288: list.add(createAccessLogElement(ch));
1289: }
1290: replace = false;
1291: } else if (ch == '%') {
1292: replace = true;
1293: list.add(new StringElement(buf.toString()));
1294: buf = new StringBuffer();
1295: } else {
1296: buf.append(ch);
1297: }
1298: }
1299: if (buf.length() > 0) {
1300: list.add(new StringElement(buf.toString()));
1301: }
1302: return (AccessLogElement[]) list
1303: .toArray(new AccessLogElement[0]);
1304: }
1305:
1306: /**
1307: * create an AccessLogElement implementation which needs header string
1308: */
1309: private AccessLogElement createAccessLogElement(String header,
1310: char pattern) {
1311: switch (pattern) {
1312: case 'i':
1313: return new HeaderElement(header);
1314: case 'c':
1315: return new CookieElement(header);
1316: case 'r':
1317: return new RequestAttributeElement(header);
1318: case 's':
1319: return new SessionAttributeElement(header);
1320: default:
1321: return new StringElement("???");
1322: }
1323: }
1324:
1325: /**
1326: * create an AccessLogElement implementation
1327: */
1328: private AccessLogElement createAccessLogElement(char pattern) {
1329: switch (pattern) {
1330: case 'a':
1331: return new RemoteAddrElement();
1332: case 'A':
1333: return new LocalAddrElement();
1334: case 'b':
1335: return new ByteSentElement(true);
1336: case 'B':
1337: return new ByteSentElement(false);
1338: case 'D':
1339: return new ElapsedTimeElement(true);
1340: case 'h':
1341: return new HostElement();
1342: case 'H':
1343: return new ProtocolElement();
1344: case 'l':
1345: return new LogicalUserNameElement();
1346: case 'm':
1347: return new MethodElement();
1348: case 'p':
1349: return new LocalPortElement();
1350: case 'q':
1351: return new QueryElement();
1352: case 'r':
1353: return new RequestElement();
1354: case 's':
1355: return new HttpStatusCodeElement();
1356: case 'S':
1357: return new SessionIdElement();
1358: case 't':
1359: return new DateAndTimeElement();
1360: case 'T':
1361: return new ElapsedTimeElement(false);
1362: case 'u':
1363: return new UserElement();
1364: case 'U':
1365: return new RequestURIElement();
1366: case 'v':
1367: return new LocalServerNameElement();
1368: default:
1369: return new StringElement("???" + pattern + "???");
1370: }
1371: }
1372: }
|