/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.valves; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.LifecycleSupport; import org.apache.catalina.util.StringManager; import org.apache.coyote.RequestInfo; import org.jboss.logging.Logger; /** *
Implementation of the Valve interface that generates a web server
* access log with the detailed line contents matching a configurable pattern.
* The syntax of the available patterns is similar to that supported by the
* Apache mod_log_config
module. As an additional feature,
* automatic rollover of log files when the date changes is also supported.
Patterns for the logged message may include constant text or any of the * following replacement strings, for which the corresponding information * from the specified Response is substituted:
*In addition, the caller can specify one of the following aliases for * commonly utilized patterns:
*%h %l %u %t "%r" %s %b
* %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}o
for outgoing response 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.
*
pattern
property.
*
* @param request Request being processed
* @param response Response being processed
*
* @exception IOException if an input/output error has occurred
* @exception ServletException if a servlet error has occurred
*/
public void invoke(Request request, Response response) throws IOException,
ServletException {
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);
}
/**
* 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) {
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;
}
}
// -------------------------------------------------------- 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) {
// 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();
}
}
}
/**
* Return the month abbreviation for the specified month, which must
* be a two-digit String.
*
* @param month Month number ("01" .. "12").
*/
private String lookup(String month) {
int index;
try {
index = Integer.parseInt(month) - 1;
} catch (Throwable t) {
index = 0; // Can not happen, in theory
}
return (months[index]);
}
/**
* Open the new log file for the date specified by dateStamp
.
*/
protected synchronized void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())
dir = new File(System.getProperty("catalina.base"), directory);
dir.mkdirs();
// Open the current log file
try {
String pathname;
// If no rotate - no need for dateStamp in fileName
if (rotatable) {
pathname = dir.getAbsolutePath() + File.separator + prefix
+ dateStamp + suffix;
} else {
pathname = dir.getAbsolutePath() + File.separator + prefix
+ suffix;
}
writer = new PrintWriter(new BufferedWriter(new FileWriter(
pathname, true), 128000), false);
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();
if ((systime - currentMillis) > 1000) {
synchronized (this) {
if ((systime - currentMillis) > 1000) {
currentDate = new Date(systime);
currentMillis = systime;
}
}
}
return currentDate;
}
private String getTimeZone(Date date) {
if (timezone.inDaylightTime(date)) {
return timeZoneDST;
} else {
return timeZoneNoDST;
}
}
private String calculateTimeZoneOffset(long offset) {
StringBuffer tz = new StringBuffer();
if ((offset < 0)) {
tz.append("-");
offset = -offset;
} else {
tz.append("+");
}
long hourOffset = offset / (1000 * 60 * 60);
long minuteOffset = (offset / (1000 * 60)) % 60;
if (hourOffset < 10)
tz.append("0");
tz.append(hourOffset);
if (minuteOffset < 10)
tz.append("0");
tz.append(minuteOffset);
return tz.toString();
}
// ------------------------------------------------------ Lifecycle Methods
/**
* Add a lifecycle event listener to this component.
*
* @param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
/**
* Remove a lifecycle event listener from this component.
*
* @param listener The listener to add
*/
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
/**
* Prepare for the beginning of active use of the public methods of this
* component. This method should be called after configure()
,
* and before any of the public methods of the component are utilized.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
public void start() throws LifecycleException {
// Validate and update our current component state
if (started)
throw new LifecycleException(sm
.getString("accessLogValve.alreadyStarted"));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Initialize the timeZone, Date formatters, and currentDate
timezone = TimeZone.getDefault();
timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
Calendar calendar = Calendar.getInstance(timezone);
int offset = calendar.get(Calendar.DST_OFFSET);
timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset);
if (fileDateFormat == null || fileDateFormat.length() == 0)
fileDateFormat = "yyyy-MM-dd";
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();
}
/**
* Gracefully terminate the active use of the public methods of this
* component. This method should be the last one called on a given
* instance of this component.
*
* @exception LifecycleException if this component detects a fatal error
* that needs to be reported
*/
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException(sm
.getString("accessLogValve.notStarted"));
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
close();
}
/**
* AccessLogElement writes the partial message into the buffer.
*/
protected interface AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time);
}
/**
* write thread name - %I
*/
protected class ThreadNameElement implements AccessLogElement {
public void addElement(StringBuffer 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 class LocalAddrElement implements AccessLogElement {
private String value = null;
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (value == null) {
synchronized (this) {
try {
value = InetAddress.getLocalHost().getHostAddress();
} catch (Throwable e) {
value = "127.0.0.1";
}
}
}
buf.append(value);
}
}
/**
* write remote IP address - %a
*/
protected class RemoteAddrElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(request.getRemoteAddr());
}
}
/**
* write remote host name - %h
*/
protected class HostElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(request.getRemoteHost());
}
}
/**
* write remote logical username from identd (always returns '-') - %l
*/
protected class LogicalUserNameElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append('-');
}
}
/**
* write request protocol - %H
*/
protected class ProtocolElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(request.getProtocol());
}
}
/**
* write remote user that was authenticated (if any), else '-' - %u
*/
protected class UserElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (request != null) {
String value = request.getRemoteUser();
if (value != null) {
buf.append(value);
} else {
buf.append('-');
}
} else {
buf.append('-');
}
}
}
/**
* write date and time, in Common Log Format - %t
*/
protected class DateAndTimeElement implements AccessLogElement {
private Date currentDate = new Date(0);
private String currentDateString = null;
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (currentDate != date) {
synchronized (this) {
if (currentDate != date) {
StringBuffer current = new StringBuffer(32);
current.append('[');
current.append(dayFormatter.format(date)); // Day
current.append('/');
current.append(lookup(monthFormatter.format(date))); // Month
current.append('/');
current.append(yearFormatter.format(date)); // Year
current.append(':');
current.append(timeFormatter.format(date)); // Time
current.append(' ');
current.append(getTimeZone(date)); // Timezone
current.append(']');
currentDateString = current.toString();
currentDate = date;
}
}
}
buf.append(currentDateString);
}
}
/**
* write first line of the request (method and request URI) - %r
*/
protected class RequestElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (request != null) {
buf.append(request.getMethod());
buf.append(' ');
buf.append(request.getRequestURI());
if (request.getQueryString() != null) {
buf.append('?');
buf.append(request.getQueryString());
}
buf.append(' ');
buf.append(request.getProtocol());
} else {
buf.append("- - ");
}
}
}
/**
* write HTTP status code of the response - %s
*/
protected class HttpStatusCodeElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (response != null) {
buf.append(response.getStatus());
} else {
buf.append('-');
}
}
}
/**
* write local port on which this request was received - %p
*/
protected class LocalPortElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(request.getServerPort());
}
}
/**
* write bytes sent, excluding HTTP headers - %b, %B
*/
protected class ByteSentElement implements AccessLogElement {
private boolean conversion;
/**
* if conversion is true, write '-' instead of 0 - %b
*/
public ByteSentElement(boolean conversion) {
this.conversion = conversion;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
long length = response.getContentCountLong() ;
if (length <= 0 && conversion) {
buf.append('-');
} else {
buf.append(length);
}
}
}
/**
* write request method (GET, POST, etc.) - %m
*/
protected class MethodElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (request != null) {
buf.append(request.getMethod());
}
}
}
/**
* write time taken to process the request - %D, %T
*/
protected class ElapsedTimeElement implements AccessLogElement {
private boolean millis;
/**
* if millis is true, write time in millis - %D
* if millis is false, write time in seconds - %T
*/
public ElapsedTimeElement(boolean millis) {
this.millis = millis;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (millis) {
buf.append(time);
} else {
// second
buf.append(time / 1000);
buf.append('.');
int remains = (int) (time % 1000);
buf.append(remains / 100);
remains = remains % 100;
buf.append(remains / 10);
buf.append(remains % 10);
}
}
}
/**
* write Query string (prepended with a '?' if it exists) - %q
*/
protected class QueryElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
String query = null;
if (request != null)
query = request.getQueryString();
if (query != null) {
buf.append('?');
buf.append(query);
}
}
}
/**
* write user session ID - %S
*/
protected class SessionIdElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (request != null) {
if (request.getSession(false) != null) {
buf.append(request.getSessionInternal(false)
.getIdInternal());
} else {
buf.append('-');
}
} else {
buf.append('-');
}
}
}
/**
* write requested URL path - %U
*/
protected class RequestURIElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (request != null) {
buf.append(request.getRequestURI());
} else {
buf.append('-');
}
}
}
/**
* write local server name - %v
*/
protected class LocalServerNameElement implements AccessLogElement {
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(request.getServerName());
}
}
/**
* write any string
*/
protected class StringElement implements AccessLogElement {
private String str;
public StringElement(String str) {
this.str = str;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
buf.append(str);
}
}
/**
* write incoming headers - %{xxx}i
*/
protected class HeaderElement implements AccessLogElement {
private String header;
public HeaderElement(String header) {
this.header = header;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
String value = request.getHeader(header);
if (value == null) {
buf.append('-');
} else {
buf.append(value);
}
}
}
/**
* write a specific cookie - %{xxx}c
*/
protected class CookieElement implements AccessLogElement {
private String header;
public CookieElement(String header) {
this.header = header;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
String value = "-";
Cookie[] c = request.getCookies();
if (c != null) {
for (int i = 0; i < c.length; i++) {
if (header.equals(c[i].getName())) {
value = c[i].getValue();
break;
}
}
}
buf.append(value);
}
}
/**
* write a specific response header - %{xxx}o
*/
protected class ResponseHeaderElement implements AccessLogElement {
private String header;
public ResponseHeaderElement(String header) {
this.header = header;
}
public void addElement(StringBuffer buf, Date date, Request request,
Response response, long time) {
if (null != response) {
String[] values = response.getHeaderValues(header);
if(values.length > 0) {
for (int i = 0; i < values.length; i++) {
String string = values[i];
buf.append(string) ;
if(i+1