You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by ca...@apache.org on 2008/06/13 21:45:39 UTC
svn commit: r667628 -
/logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java
Author: carnold
Date: Fri Jun 13 12:45:38 2008
New Revision: 667628
URL: http://svn.apache.org/viewvc?rev=667628&view=rev
Log:
Bug 45165: MultiFileAppender implementation
Added:
logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java
Added: logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java
URL: http://svn.apache.org/viewvc/logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java?rev=667628&view=auto
==============================================================================
--- logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java (added)
+++ logging/sandbox/log4j/multifile/src/main/java/org/apache/log4j/multifile/MultiFileAppender.java Fri Jun 13 12:45:38 2008
@@ -0,0 +1,526 @@
+/*
+ * Copyright 2008 The Apache Software Foundation.
+ *
+ * Licensed 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.log4j.multifile;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.CountingQuietWriter;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.QuietWriter;
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.LoggingEvent;
+
+public class MultiFileAppender extends AppenderSkeleton {
+
+ /**
+ * Do we do bufferedIO?
+ */
+ private boolean bufferedIO = false;
+
+ /**
+ * Determines the size of IO buffer be. Default is 8K.
+ */
+ private int bufferSize = 8 * 1024;
+
+ /**
+ * Immediate flush means that the underlying writer or output stream will be
+ * flushed at the end of each append operation. Immediate flush is slower
+ * but ensures that each append request is actually written. If
+ * <code>immediateFlush</code> is set to <code>false</code>, then there
+ * is a good chance that the last few logs events are not actually written
+ * to persistent media if and when the application crashes.
+ *
+ * <p>
+ * The <code>immediateFlush</code> variable is set to <code>true</code>
+ * by default.
+ *
+ */
+ private boolean immediateFlush = true;
+
+ /**
+ * The encoding to use when writing.
+ * <p>
+ * The <code>encoding</code> variable is set to <code>null</null> by
+ * default which results in the utilization of the system's default
+ * encoding.
+ */
+ private String encoding;
+
+ /**
+ * The name of the default log file. This is used if the FileNamePolicy
+ * returns <code>null</code> for the filename.
+ */
+ private String defaultFileName;
+
+ /**
+ * The filename policy that determines log file name for each logging event
+ */
+ private FileNamePolicy fileNamePolicy;
+
+ /**
+ *
+ */
+ private int maxOpenFiles = 10;
+
+ /**
+ * The time in milliseconds after which an unused file is closed - default
+ * is 5 minutes
+ */
+ private long idleTime = 300 * 1000;
+
+ private Date nextCleanup = new Date();
+
+ /**
+ * Maps log filenames to the <code>SubAppenders</code> for each open file
+ */
+ private Map openFileMap = new HashMap(maxOpenFiles * 4 / 3);
+
+ /**
+ * list to store the <code>MultiFileAppenderListeners</code>
+ */
+ private List listeners = new ArrayList();
+
+ /**
+ * Default Constructor
+ */
+ public MultiFileAppender() {
+ // does nothing
+ }
+
+ /**
+ *
+ * @param layout
+ * @param defaultFileName
+ * @param fileNamePolicy
+ * @param bufferedIO
+ * @param bufferSize
+ */
+ public MultiFileAppender(Layout layout, String defaultFileName,
+ FileNamePolicy fileNamePolicy, boolean bufferedIO, int bufferSize) {
+ this.layout = layout;
+ this.setBufferedIO(bufferedIO);
+ this.bufferSize = bufferSize;
+ this.defaultFileName = defaultFileName;
+ this.fileNamePolicy = fileNamePolicy;
+ }
+
+ /**
+ *
+ * @param layout
+ * @param defaultFileName
+ * @param fileNamePolicy
+ */
+ public MultiFileAppender(Layout layout, String defaultFileName,
+ FileNamePolicy fileNamePolicy) {
+ this.layout = layout;
+ this.defaultFileName = defaultFileName;
+ this.fileNamePolicy = fileNamePolicy;
+ }
+
+ public void append(LoggingEvent event) {
+ if (!checkEntryConditions()) {
+ return;
+ }
+
+ // determine log file name
+ String fileName = null;
+ if (null != fileNamePolicy) {
+ fileName = fileNamePolicy.getFileName(event);
+ }
+ if (null == fileName) {
+ if (null == defaultFileName) {
+ errorHandler
+ .error("No defaultFileName set for the appender named ["
+ + name + "].");
+ return;
+ }
+ fileName = defaultFileName;
+ }
+
+ subAppend(fileName, event);
+
+ cleanupIdleFiles();
+ }
+
+ protected boolean checkEntryConditions() {
+ if (this.closed) {
+ LogLog.warn("Not allowed to write to a closed appender.");
+ return false;
+ }
+ if (null == this.layout) {
+ errorHandler.error("No layout set for the appender named [" + name
+ + "].");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * get the file and append log message
+ *
+ * @param fileName
+ * @param event
+ */
+ protected void subAppend(String fileName, LoggingEvent event) {
+ LogFileEntry lfe;
+ // is the file already open
+ lfe = (LogFileEntry) openFileMap.get(fileName);
+ // else open/create the new file
+ if (null == lfe) {
+ lfe = openFile(fileName);
+ }
+
+ if (null != lfe) {
+ // append log event to file
+ subAppendToWriter(lfe.getQw(), event);
+
+ // fire fileAppended event
+ fireFileAppendedEvent(fileName, lfe.getQw().getCount());
+
+ // update access time
+ lfe.setLastAccess(new Date());
+ }
+ }
+
+ protected void subAppendToWriter(QuietWriter qw, LoggingEvent event) {
+ qw.write(this.layout.format(event));
+
+ if (layout.ignoresThrowable()) {
+ String[] s = event.getThrowableStrRep();
+ if (s != null) {
+ int len = s.length;
+ for (int i = 0; i < len; i++) {
+ qw.write(s[i]);
+ qw.write(Layout.LINE_SEP);
+ }
+ }
+ }
+
+ if (this.immediateFlush) {
+ qw.flush();
+ }
+ }
+
+ protected void cleanupIdleFiles() {
+ Date now = new Date();
+ // cleanup:
+ // if too many open files or time to run next check close old files
+ if (now.after(this.nextCleanup) || openFileMap.size() > maxOpenFiles) {
+ Iterator openFileIt = openFileMap.values().iterator();
+ List toDelete = new ArrayList();
+ while (openFileIt.hasNext()) {
+ LogFileEntry lfe = (LogFileEntry) openFileIt.next();
+ if (lfe.getLastAccess().getTime() + idleTime < now.getTime()) {
+ toDelete.add(lfe);
+ }
+ }
+ Iterator toDeleteIt = toDelete.iterator();
+ while (toDeleteIt.hasNext()) {
+ LogFileEntry lfe = (LogFileEntry) toDeleteIt.next();
+ openFileMap.remove(lfe.getFileName());
+ closeFile(lfe);
+ }
+ // TODO should we rather use shorter intervalls than the idleTime?
+ nextCleanup = new Date(now.getTime() + idleTime);
+ }
+ if (openFileMap.size() > maxOpenFiles) {
+ LogLog.warn("appender ["
+ + this.name
+ + "] has still too many open files after cleanupIdleFiles(): "
+ + openFileMap.size() + " open files");
+ }
+ }
+
+ public synchronized void close() {
+ if (this.closed) {
+ return;
+ }
+ this.closed = true;
+ Iterator openFileIt = openFileMap.values().iterator();
+ while (openFileIt.hasNext()) {
+ closeFile((LogFileEntry) openFileIt.next());
+ }
+ openFileMap.clear();
+ }
+
+ protected LogFileEntry openFile(String fileName) {
+ FileOutputStream ostream = null;
+ try {
+ //
+ // attempt to create file
+ //
+ ostream = new FileOutputStream(fileName, true);
+ } catch (FileNotFoundException ex) {
+ //
+ // if parent directory does not exist then
+ // attempt to create it and try to create file
+ // see bug 9150
+ //
+ try {
+ String parentName = new File(fileName).getParent();
+ if (parentName != null) {
+ File parentDir = new File(parentName);
+ if (!parentDir.exists() && parentDir.mkdirs()) {
+ ostream = new FileOutputStream(fileName, true);
+ } else {
+ throw ex;
+ }
+ } else {
+ throw ex;
+ }
+ } catch (FileNotFoundException e) {
+ errorHandler.error("openFile(" + fileName + ") call failed", e,
+ ErrorCode.FILE_OPEN_FAILURE);
+ return null;
+ }
+ }
+
+ Writer writer = createWriter(ostream);
+ if (bufferedIO) {
+ writer = new BufferedWriter(writer, bufferSize);
+ }
+
+ CountingQuietWriter qw = new CountingQuietWriter(writer, errorHandler);
+
+ LogFileEntry lfe = new LogFileEntry(fileName, qw);
+ // add file into hash map
+ openFileMap.put(fileName, lfe);
+
+ writeHeader(qw);
+
+ return lfe;
+ }
+
+ /**
+ * Write a footer as produced by the embedded layout's {@link
+ * Layout#getFooter} method.
+ */
+ protected void writeFooter(QuietWriter qw) {
+ if (layout != null) {
+ String f = layout.getFooter();
+ if (f != null && qw != null) {
+ qw.write(f);
+ qw.flush();
+ }
+ }
+ }
+
+ /**
+ * Write a header as produced by the embedded layout's {@link
+ * Layout#getHeader} method.
+ */
+ protected void writeHeader(QuietWriter qw) {
+ if (layout != null) {
+ String h = layout.getHeader();
+ if (h != null && qw != null)
+ qw.write(h);
+ }
+ }
+
+ /**
+ * Returns an OutputStreamWriter when passed an OutputStream. The encoding
+ * used will depend on the value of the <code>encoding</code> property. If
+ * the encoding value is specified incorrectly the writer will be opened
+ * using the default system encoding (an error message will be printed to
+ * the loglog.
+ */
+ protected OutputStreamWriter createWriter(OutputStream os) {
+ OutputStreamWriter retval = null;
+
+ String enc = getEncoding();
+ if (enc != null) {
+ try {
+ retval = new OutputStreamWriter(os, enc);
+ } catch (IOException e) {
+ LogLog.warn("Error initializing output writer.");
+ LogLog.warn("Unsupported encoding?");
+ }
+ }
+ if (retval == null) {
+ retval = new OutputStreamWriter(os);
+ }
+ return retval;
+ }
+
+ protected void closeFile(LogFileEntry logFile) {
+ writeFooter(logFile.getQw());
+
+ try {
+ logFile.getQw().close();
+ } catch (IOException e) {
+ errorHandler.error("Could not close " + logFile.getQw(), e,
+ ErrorCode.CLOSE_FAILURE);
+ }
+ }
+
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ public boolean isBufferedIO() {
+ return bufferedIO;
+ }
+
+ public void setBufferedIO(boolean bufferedIO) {
+ this.bufferedIO = bufferedIO;
+ if (bufferedIO) {
+ this.immediateFlush = false;
+ }
+ }
+
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public String getDefaultFileName() {
+ return defaultFileName;
+ }
+
+ public void setDefaultFileName(String defaultFileName) {
+ this.defaultFileName = defaultFileName.trim();
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public long getIdleTime() {
+ return idleTime;
+ }
+
+ public void setIdleTime(long idleTime) {
+ this.idleTime = idleTime;
+ }
+
+ public boolean isImmediateFlush() {
+ return immediateFlush;
+ }
+
+ public void setImmediateFlush(boolean immediateFlush) {
+ this.immediateFlush = immediateFlush;
+ }
+
+ public int getMaxOpenFiles() {
+ return maxOpenFiles;
+ }
+
+ public void setMaxOpenFiles(int openFileLimit) {
+ this.maxOpenFiles = openFileLimit;
+ }
+
+ public FileNamePolicy getFileNamePolicy() {
+ return fileNamePolicy;
+ }
+
+ public void setFileNamePolicy(FileNamePolicy fileNamePolicy) {
+ this.fileNamePolicy = fileNamePolicy;
+ }
+
+ public void activateOptions() {
+ // do nothing
+ }
+
+ protected void fireFileAppendedEvent(String fileName, long fileSize) {
+ Iterator listenerIt = this.listeners.iterator();
+ while (listenerIt.hasNext()) {
+ ((MultiFileAppenderListener) listenerIt.next()).fileAppended(
+ fileName, fileSize);
+ }
+ }
+
+ /**
+ * Add a MultiFileAppenderListener to this MultiFileAppender
+ *
+ * @param listener
+ */
+ public void addMultiFileAppenderListener(MultiFileAppenderListener listener) {
+ this.listeners.add(listener);
+ }
+
+ /**
+ * Remove a MultiFileAppenderListener from this MultiFileAppender
+ *
+ * @param listener
+ */
+ public void removeMultiFileAppenderListener(
+ MultiFileAppenderListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ protected static final class LogFileEntry {
+
+ private String fileName;
+
+ private CountingQuietWriter qw;
+
+ private Date lastAccess;
+
+ public LogFileEntry(String fileName, CountingQuietWriter qw) {
+ this.fileName = fileName;
+ this.qw = qw;
+ this.lastAccess = new Date();
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public Date getLastAccess() {
+ return lastAccess;
+ }
+
+ public void setLastAccess(Date lastAccess) {
+ this.lastAccess = lastAccess;
+ }
+
+ public CountingQuietWriter getQw() {
+ return qw;
+ }
+
+ public void setQw(CountingQuietWriter qw) {
+ this.qw = qw;
+ }
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org