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 [EMAIL PROTECTED]
+        * 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 [EMAIL PROTECTED]
+        * 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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to