init
This commit is contained in:
704
java/org/apache/catalina/valves/AccessLogValve.java
Normal file
704
java/org/apache/catalina/valves/AccessLogValve.java
Normal file
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* 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.CharArrayWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.ExceptionUtils;
|
||||
import org.apache.tomcat.util.buf.B2CConverter;
|
||||
|
||||
|
||||
/**
|
||||
* This is a concrete implementation of {@link AbstractAccessLogValve} that
|
||||
* outputs the access log to a file. The features of this implementation
|
||||
* include:
|
||||
* <ul>
|
||||
* <li>Automatic date-based rollover of log files</li>
|
||||
* <li>Optional log file rotation</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* For UNIX users, another field called <code>checkExists</code> is also
|
||||
* available. If set to true, the log file's existence will be checked before
|
||||
* each logging. This way an external log rotator can move the file
|
||||
* somewhere and Tomcat will start with a new file.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* For JMX junkies, a public method called <code>rotate</code> has
|
||||
* been made available to allow you to tell this instance to move
|
||||
* the existing log file to somewhere else and start writing a new log file.
|
||||
* </p>
|
||||
*/
|
||||
public class AccessLogValve extends AbstractAccessLogValve {
|
||||
|
||||
private static final Log log = LogFactory.getLog(AccessLogValve.class);
|
||||
|
||||
//------------------------------------------------------ Constructor
|
||||
public AccessLogValve() {
|
||||
super();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------- 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 prefix that is added to log file filenames.
|
||||
*/
|
||||
protected volatile String prefix = "access_log";
|
||||
|
||||
|
||||
/**
|
||||
* Should we rotate our log file? Default is true (like old behavior)
|
||||
*/
|
||||
protected boolean rotatable = true;
|
||||
|
||||
/**
|
||||
* Should we defer inclusion of the date stamp in the file
|
||||
* name until rotate time? Default is false.
|
||||
*/
|
||||
protected boolean renameOnRotate = false;
|
||||
|
||||
|
||||
/**
|
||||
* Buffered logging.
|
||||
*/
|
||||
private boolean buffered = true;
|
||||
|
||||
|
||||
/**
|
||||
* The suffix that is added to log file filenames.
|
||||
*/
|
||||
protected volatile String suffix = "";
|
||||
|
||||
|
||||
/**
|
||||
* The PrintWriter to which we are currently logging, if any.
|
||||
*/
|
||||
protected PrintWriter writer = null;
|
||||
|
||||
|
||||
/**
|
||||
* A date formatter to format a Date using the format
|
||||
* given by <code>fileDateFormat</code>.
|
||||
*/
|
||||
protected SimpleDateFormat fileDateFormatter = null;
|
||||
|
||||
|
||||
/**
|
||||
* The current log file we are writing to. Helpful when checkExists
|
||||
* is true.
|
||||
*/
|
||||
protected File currentLogFile = null;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Date format to place in log file name.
|
||||
*/
|
||||
protected String fileDateFormat = ".yyyy-MM-dd";
|
||||
|
||||
/**
|
||||
* Character set used by the log file. If it is <code>null</code>, the
|
||||
* system default character set will be used. An empty string will be
|
||||
* treated as <code>null</code> when this property is assigned.
|
||||
*/
|
||||
protected volatile String encoding = null;
|
||||
|
||||
/**
|
||||
* The number of days to retain the access log files before they are
|
||||
* removed.
|
||||
*/
|
||||
private int maxDays = -1;
|
||||
private volatile boolean checkForOldLogs = false;
|
||||
|
||||
// ------------------------------------------------------------- Properties
|
||||
|
||||
|
||||
public int getMaxDays() {
|
||||
return maxDays;
|
||||
}
|
||||
|
||||
|
||||
public void setMaxDays(int maxDays) {
|
||||
this.maxDays = maxDays;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for file existence before logging.
|
||||
* @return <code>true</code> if file existence is checked first
|
||||
*/
|
||||
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 access log.
|
||||
*
|
||||
* @return <code>true</code> if the access log should be rotated
|
||||
*/
|
||||
public boolean isRotatable() {
|
||||
return rotatable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure whether the access log should be rotated.
|
||||
*
|
||||
* @param rotatable true if the log should be rotated
|
||||
*/
|
||||
public void setRotatable(boolean rotatable) {
|
||||
this.rotatable = rotatable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Should we defer inclusion of the date stamp in the file
|
||||
* name until rotate time.
|
||||
* @return <code>true</code> if the logs file names are time stamped
|
||||
* only when they are rotated
|
||||
*/
|
||||
public boolean isRenameOnRotate() {
|
||||
return renameOnRotate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the value if we should defer inclusion of the date
|
||||
* stamp in the file name until rotate time
|
||||
*
|
||||
* @param renameOnRotate true if defer inclusion of date stamp
|
||||
*/
|
||||
public void setRenameOnRotate(boolean renameOnRotate) {
|
||||
this.renameOnRotate = renameOnRotate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the logging buffered. Usually buffering can increase performance.
|
||||
* @return <code>true</code> if the logging uses a buffer
|
||||
*/
|
||||
public boolean isBuffered() {
|
||||
return buffered;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the value if the logging should be buffered
|
||||
*
|
||||
* @param buffered <code>true</code> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the date format date based log rotation.
|
||||
*/
|
||||
public String getFileDateFormat() {
|
||||
return fileDateFormat;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the date format date based log rotation.
|
||||
* @param fileDateFormat The format for the file timestamp
|
||||
*/
|
||||
public void setFileDateFormat(String fileDateFormat) {
|
||||
String newFormat;
|
||||
if (fileDateFormat == null) {
|
||||
newFormat = "";
|
||||
} else {
|
||||
newFormat = fileDateFormat;
|
||||
}
|
||||
this.fileDateFormat = newFormat;
|
||||
|
||||
synchronized (this) {
|
||||
fileDateFormatter = new SimpleDateFormat(newFormat, Locale.US);
|
||||
fileDateFormatter.setTimeZone(TimeZone.getDefault());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the character set name that is used to write the log file.
|
||||
*
|
||||
* @return Character set name, or <code>null</code> if the system default
|
||||
* character set is used.
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the character set that is used to write the log file.
|
||||
*
|
||||
* @param encoding The name of the character set.
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
if (encoding != null && encoding.length() > 0) {
|
||||
this.encoding = encoding;
|
||||
} else {
|
||||
this.encoding = null;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------- 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();
|
||||
}
|
||||
|
||||
int maxDays = this.maxDays;
|
||||
String prefix = this.prefix;
|
||||
String suffix = this.suffix;
|
||||
|
||||
if (rotatable && checkForOldLogs && maxDays > 0) {
|
||||
long deleteIfLastModifiedBefore =
|
||||
System.currentTimeMillis() - (maxDays * 24L * 60 * 60 * 1000);
|
||||
File dir = getDirectoryFile();
|
||||
if (dir.isDirectory()) {
|
||||
String[] oldAccessLogs = dir.list();
|
||||
|
||||
if (oldAccessLogs != null) {
|
||||
for (String oldAccessLog : oldAccessLogs) {
|
||||
boolean match = false;
|
||||
|
||||
if (prefix != null && prefix.length() > 0) {
|
||||
if (!oldAccessLog.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (suffix != null && suffix.length() > 0) {
|
||||
if (!oldAccessLog.endsWith(suffix)) {
|
||||
continue;
|
||||
}
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
File file = new File(dir, oldAccessLog);
|
||||
if (file.isFile() && file.lastModified() < deleteIfLastModifiedBefore) {
|
||||
if (!file.delete()) {
|
||||
log.warn(sm.getString(
|
||||
"accessLogValve.deleteFail", file.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForOldLogs = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the log file if necessary.
|
||||
*/
|
||||
public void rotate() {
|
||||
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(true);
|
||||
dateStamp = tsDate;
|
||||
open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(false);
|
||||
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
|
||||
|
||||
|
||||
private File getDirectoryFile() {
|
||||
File dir = new File(directory);
|
||||
if (!dir.isAbsolute()) {
|
||||
dir = new File(getContainer().getCatalinaBase(), directory);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a File object based on the current log file name.
|
||||
* Directories are created as needed but the underlying file
|
||||
* is not created or opened.
|
||||
*
|
||||
* @param useDateStamp include the timestamp in the file name.
|
||||
* @return the log file object
|
||||
*/
|
||||
private File getLogFile(boolean useDateStamp) {
|
||||
// Create the directory if necessary
|
||||
File dir = getDirectoryFile();
|
||||
if (!dir.mkdirs() && !dir.isDirectory()) {
|
||||
log.error(sm.getString("accessLogValve.openDirFail", dir));
|
||||
}
|
||||
|
||||
// Calculate the current log file name
|
||||
File pathname;
|
||||
if (useDateStamp) {
|
||||
pathname = new File(dir.getAbsoluteFile(), prefix + dateStamp
|
||||
+ suffix);
|
||||
} else {
|
||||
pathname = new File(dir.getAbsoluteFile(), prefix + suffix);
|
||||
}
|
||||
File parent = pathname.getParentFile();
|
||||
if (!parent.mkdirs() && !parent.isDirectory()) {
|
||||
log.error(sm.getString("accessLogValve.openDirFail", parent));
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a current but rotated log file back to the unrotated
|
||||
* one. Needed if date stamp inclusion is deferred to rotation
|
||||
* time.
|
||||
*/
|
||||
private void restore() {
|
||||
File newLogFile = getLogFile(false);
|
||||
File rotatedLogFile = getLogFile(true);
|
||||
if (rotatedLogFile.exists() && !newLogFile.exists() &&
|
||||
!rotatedLogFile.equals(newLogFile)) {
|
||||
try {
|
||||
if (!rotatedLogFile.renameTo(newLogFile)) {
|
||||
log.error(sm.getString("accessLogValve.renameFail", rotatedLogFile, newLogFile));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.handleThrowable(e);
|
||||
log.error(sm.getString("accessLogValve.renameFail", rotatedLogFile, newLogFile), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close the currently open log file (if any)
|
||||
*
|
||||
* @param rename Rename file to final name after closing
|
||||
*/
|
||||
private synchronized void close(boolean rename) {
|
||||
if (writer == null) {
|
||||
return;
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
if (rename && renameOnRotate) {
|
||||
File newLogFile = getLogFile(true);
|
||||
if (!newLogFile.exists()) {
|
||||
try {
|
||||
if (!currentLogFile.renameTo(newLogFile)) {
|
||||
log.error(sm.getString("accessLogValve.renameFail", currentLogFile, newLogFile));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.handleThrowable(e);
|
||||
log.error(sm.getString("accessLogValve.renameFail", currentLogFile, newLogFile), e);
|
||||
}
|
||||
} else {
|
||||
log.error(sm.getString("accessLogValve.alreadyExists", currentLogFile, newLogFile));
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void log(CharArrayWriter message) {
|
||||
|
||||
rotate();
|
||||
|
||||
/* In case something external rotated the file instead */
|
||||
if (checkExists) {
|
||||
synchronized (this) {
|
||||
if (currentLogFile != null && !currentLogFile.exists()) {
|
||||
try {
|
||||
close(false);
|
||||
} 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
|
||||
try {
|
||||
message.write(System.lineSeparator());
|
||||
synchronized(this) {
|
||||
if (writer != null) {
|
||||
message.writeTo(writer);
|
||||
if (!buffered) {
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.warn(sm.getString(
|
||||
"accessLogValve.writeFail", message.toString()), ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open the new log file for the date specified by <code>dateStamp</code>.
|
||||
*/
|
||||
protected synchronized void open() {
|
||||
// Open the current log file
|
||||
// If no rotate - no need for dateStamp in fileName
|
||||
File pathname = getLogFile(rotatable && !renameOnRotate);
|
||||
|
||||
Charset charset = null;
|
||||
if (encoding != null) {
|
||||
try {
|
||||
charset = B2CConverter.getCharset(encoding);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
log.error(sm.getString(
|
||||
"accessLogValve.unsupportedEncoding", encoding), ex);
|
||||
}
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
|
||||
try {
|
||||
writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(pathname, true), charset), 128000),
|
||||
false);
|
||||
|
||||
currentLogFile = pathname;
|
||||
} catch (IOException e) {
|
||||
writer = null;
|
||||
currentLogFile = null;
|
||||
log.error(sm.getString("accessLogValve.openFail", pathname), e);
|
||||
}
|
||||
// Rotating a log file will always trigger a new file to be opened so
|
||||
// when a new file is opened, check to see if any old files need to be
|
||||
// removed.
|
||||
checkForOldLogs = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Date formatters
|
||||
String format = getFileDateFormat();
|
||||
fileDateFormatter = new SimpleDateFormat(format, Locale.US);
|
||||
fileDateFormatter.setTimeZone(TimeZone.getDefault());
|
||||
dateStamp = fileDateFormatter.format(new Date(System.currentTimeMillis()));
|
||||
if (rotatable && renameOnRotate) {
|
||||
restore();
|
||||
}
|
||||
open();
|
||||
|
||||
super.startInternal();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
super.stopInternal();
|
||||
close(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user