
org.glassfish.grizzly.http.server.accesslog.RotatingFileAppender Maven / Gradle / Ivy
/*
* Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.grizzly.http.server.accesslog;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.server.HttpServer;
/**
* An {@link AccessLogAppender appender} writing log entries to {@link File}s, and rotating/archiving them when
* necessary.
*
* @author Pier Fumagalli
* @author USRZ.com
*/
public class RotatingFileAppender implements AccessLogAppender {
private static final Logger LOGGER = Grizzly.logger(HttpServer.class);
/* The SDF that will format the "current" file name */
private final SimpleDateFormatThreadLocal fileFormat;
/* The SDF that will format the "archive" file name */
private final SimpleDateFormatThreadLocal archiveFormat;
/* Our current file appender */
private FileAppender appender;
/* The directory where to keep files */
private final File directory;
/* The name of the current archive file name */
private File currentArchive;
/* The name of the file we're actualy writing to */
private File currentFile;
/* Flag, closed, byebye */
private boolean closed;
/**
* Create a {@link RotatingFileAppender} writing access log files in the specified directory and using the specified
* {@link SimpleDateFormat} pattern to generate file names.
*
*
* For example when the specified pattern is 'access-'yyyyMMDDhh'.log'
(note the quotes), access log files
* will be rotated on a hourly basis, and the output will be written to files like access-2013120422.log
,
* access-2013120423.log
, ... and so on.
*
* @param directory The directory where access log files will be written to.
* @param filePattern A properly escaped {@link SimpleDateFormat} pattern for the access log files.
* @throws IOException If an I/O error occurred accessing the filesystem.
*/
public RotatingFileAppender(File directory, String filePattern) throws IOException {
this(filePattern, filePattern, directory);
LOGGER.fine("Creating rotating log appender in \"" + directory + "\" with file pattern \"" + filePattern + "\"");
}
/**
* Create a {@link RotatingFileAppender} writing access log files in the specified directory.
*
*
* When using this constructor the current log file (the one being written to) will always be the one
* identified by the fileName
parameter, and then archival of files will be delegated to the archive
* pattern.
*
*
*
* For example when fileName
is current.log
and archivePattern
is
* 'archive-'yyyyMMDD'.log'
(note the quotes), access logs will be written to the current.log
* file and this file will be rotated on a daily basis to files like archive-20131204.log
,
* archive-20131205.log
, ... and so on.
*
* @param directory The directory where access log files will be written to.
* @param fileName A file name where log entries will be written to.
* @param archivePattern A properly escaped {@link SimpleDateFormat} pattern for the access log archive files.
* @throws IOException If an I/O error occurred accessing the filesystem.
*/
public RotatingFileAppender(File directory, String fileName, String archivePattern) throws IOException {
this(escape(fileName), archivePattern, directory);
LOGGER.fine("Creating rotating log appender in \"" + directory + "\" writing to \"" + fileName + "\" and archive pattern \"" + archivePattern + "\"");
}
/* ====================================================================== */
private static String escape(String fileName) {
if (fileName == null) {
throw new NullPointerException("Null file name");
}
return "'" + fileName.replace("'", "''") + "'";
}
/* ====================================================================== */
private RotatingFileAppender(String filePattern, String archivePattern, File directory) throws IOException {
this.directory = directory.getCanonicalFile();
archiveFormat = new SimpleDateFormatThreadLocal(archivePattern);
fileFormat = new SimpleDateFormatThreadLocal(filePattern);
final Date now = new Date();
currentArchive = new File(directory, archiveFormat.get().format(now)).getCanonicalFile();
currentFile = new File(directory, fileFormat.get().format(now)).getCanonicalFile();
/* Validate the arguments */
if (!this.directory.equals(currentArchive.getParentFile())) {
throw new IllegalArgumentException("Archive file \"" + currentArchive + "\" is not a child of the configured directory \"" + this.directory + "\"");
}
if (!this.directory.equals(currentFile.getParentFile())) {
throw new IllegalArgumentException("Access log file \"" + currentFile + "\" is not a child of the configured directory \"" + this.directory + "\"");
}
if (currentArchive.equals(currentFile)) {
throw new IllegalArgumentException("Access log file and archive file point to the same file \"" + currentFile + "\"");
}
/* Validated, we can open files */
appender = new FileAppender(currentFile, true);
}
/* ====================================================================== */
/* DO SOME ACTUAL WORK */
/* ====================================================================== */
@Override
public void append(String accessLogEntry) throws IOException {
if (closed) {
return;
}
/* It's all about date and time */
final Date date = new Date();
synchronized (this) {
/* Calculate the name of the current archive */
final SimpleDateFormat archiveFormat = this.archiveFormat.get();
final File archive = new File(directory, archiveFormat.format(date));
/* If this archive is *NOT* the one we wrote to last, rotate */
if (!archive.equals(currentArchive)) {
try {
/* Close our current appender */
appender.close();
/* If we have different file names, move the file to archive */
if (!currentFile.equals(currentArchive)) {
LOGGER.info("Archiving \"" + currentFile + "\" to \"" + currentArchive + "\"");
if (!currentFile.renameTo(currentArchive)) {
throw new IOException("Unable to rename \"" + currentFile + "\" to \"" + currentArchive + "\"");
}
}
/* Save our new state */
currentArchive = archive;
currentFile = new File(directory, fileFormat.get().format(date));
/* Create our new appender */
appender = new FileAppender(currentFile, true);
} catch (IOException exception) {
LOGGER.log(WARNING, "I/O error rotating access log file", exception);
}
}
appender.append(accessLogEntry);
}
}
@Override
public void close() throws IOException {
closed = true;
appender.close();
}
}