/*
* 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 = 0 L;
/**
* 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 > , UTF - 8 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 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 * 24 L * 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.UTF_8;
}
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, System.getProperty("user.name" )), 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 );
}
}
Messung V0.5 in Prozent C=91 H=98 G=94
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-10-04)
¤
*© Formatika GbR, Deutschland