/* Copyright 1996-2008 Ariba, Inc. 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. $Id: //ariba/platform/util/core/ariba/util/log/ArchivingAppender.java#10 $ */ package ariba.util.log; import ariba.util.core.Fmt; import ariba.util.core.ListUtil; import ariba.util.core.StringUtil; import ariba.util.i18n.I18NUtil; import ariba.util.formatter.IntegerFormatter; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Iterator; import org.apache.log4j.FileAppender; import org.apache.log4j.helpers.LogLog; /** This class extends org.apache.log4j.FileAppender to provide archiving support. It allows one to optionally specify the name of the directory of the file to archive and the archiving directory where the acrhived file will reside. @aribaapi documented */ public class ArchivingAppender extends FileAppender { /** Property for what the name of the log file will be @aribaapi ariba */ public static final String LogFileNameKey = "LogFileName"; /** Property for what the directory of the log file will be @aribaapi ariba */ public static final String DirectoryNameKey = "DirectoryName"; /** Property for where the log file will be archive to @aribaapi ariba */ public static final String ArchiveDirectoryNameKey = "ArchiveDirectoryName"; /** Property for how large the archive directory can grow before being pruned. @aribaapi ariba */ public static final String MaxArchiveSizeKey = "MaxArchiveSize"; /** All of the stuff that is crucial to making file building and archiving work. @aribaapi private */ private static final int DefaultLogMaxMegaBytes = -1; /** The default encoding to be used. @aribaapi private */ public static final String DefaultEncoding = I18NUtil.EncodingUTF_8; /** List of all instances of this class @aribaapi private */ private static List allInstances = ListUtil.list(); /** @aribaapi private */ private ConstrainedDirectory directory; /** @aribaapi private */ protected ConstrainedDirectory archiveDirectory; /** @aribaapi private */ private String fileBaseName; /** @aribaapi private */ private String lastArchivedFileName; /** @aribaapi private */ private String maxArchiveSize; /** @aribaapi private */ private String directoryName; /** @aribaapi private */ private String archiveDirectoryName; /** Construct an ArchivingAppender. @aribaapi documented */ public ArchivingAppender () { super(); synchronized (allInstances) { allInstances.add(this); } } /** @return an iterator of all ArchivingAppender instances */ public static Iterator getIteratorForAppenders () { synchronized (allInstances) { return ListUtil.cloneList(allInstances).iterator(); } } /** Removes this ArchivingAppender from the list of allInstances. @aribaapi ariba */ public void removeFromAllInstances () { synchronized (allInstances) { allInstances.remove(this); } } /** Gets the name of the most recent archived log file. @aribaapi ariba @return name of most recent archived log file */ public String getLastArchivedFileName () { return lastArchivedFileName; } /** Set the directory for the log file, should be called prior to calling #activateOptions. Note that this is not synchronized, the last caller wins. @param directoryName directory as String @aribaapi documented */ public void setDirectoryName (String directoryName) { this.directoryName = directoryName; } /** Returns the directory for the log file. @return the directory for the log file. @aribaapi documented */ public String getDirectoryName () { return directoryName; } /** Set the archive directory for the log file. Should be called prior to calling #activateOptions. Note that this is not synchronized, the last caller wins. @param archiveDirectoryName directory as String @aribaapi documented */ public void setArchiveDirectoryName (String archiveDirectoryName) { this.archiveDirectoryName = archiveDirectoryName; } /** @return the archive directory for the log file @aribaapi documented */ public String getArchiveDirectoryName () { return archiveDirectoryName; } /** Set the max archive directory size @param maxArchiveSize size in mega bytes @aribaapi documented */ public void setMaxArchiveSize (String maxArchiveSize) { this.maxArchiveSize = maxArchiveSize; } /** @return max archive directory size @aribaapi documented */ public String getMaxArchiveSize () { return maxArchiveSize; } /** Archive the currently opened log file @aribaapi private */ public void archiveLogFile () { synchronized (this) { closeFile(); createLogFile(fileBaseName); try { setFile(fileName, fileAppend, bufferedIO, bufferSize); } catch (IOException e) { } } } /** Create the standard directories that are needed for logging information from the specified Ariba product. @return a boolean value representing whether the initialization was sucessful. @aribaapi private */ public boolean initLogDirectories () { boolean success = this.directory.instantiate(); if (!success) { this.directory.LogError("Unable to instantiate log directory"); } boolean archiveSuccess = this.archiveDirectory.instantiate(); if (!archiveSuccess) { this.archiveDirectory.LogError( "Unable to instantiate log archive directory"); } return (success && archiveSuccess); } /** Create a new LogFile that is guaranteed to exist and adhere to the constraints set forth by the log file manager (e.g. disk use) If the file already exists an exception is raised @param fileName name of the log file to create @return a LogFile that can be written to. @aribaapi private */ protected LogFile createLogFile (String fileName) { File rootDirectory = this.directory; lastArchivedFileName = null; // generate the file path, the constructor does not // materialize or verify the backing file int indexOfExtension = fileName.lastIndexOf('.'); String fileSuffix = ""; String filePrefix = null; if (indexOfExtension != -1) { fileSuffix = fileName.substring(indexOfExtension + 1); filePrefix = fileName.substring(0, indexOfExtension); } else { filePrefix = fileName; } LogFile logFile = new LogFile(rootDirectory, filePrefix, fileSuffix); boolean success = true; if (logFile.file().exists()) { File rootArchiveDirectory = this.archiveDirectory; success = logFile.moveTo(rootArchiveDirectory); if (!success) { LogLog.error("Unable to archive old log"); } } lastArchivedFileName = logFile.archivedFileName(); return logFile; } /** @aribaapi private */ public void monitorDirectories () { this.initLogDirectories(); /* Since we shouldn't be creating log directories all the time the overhead to validate, init log directories is worthwhile. Someday, the log and archive directory will be dynamic and hence could have changed since the last log was created. */ this.archiveDirectory.purgeOnConstraint(); } /** Active the options set for this class. The log file is created when this method is executed. @aribaapi documented */ public void activateOptions () { if (getEncoding() == null) { setEncoding(DefaultEncoding); } if (directoryName == null) { directoryName = LogManager.getDirectoryName(); } setAppend(false); directory = new ConstrainedDirectory(directoryName); fileBaseName = fileName; fileName = Fmt.S("%s/%s", directoryName, fileName); String archiveDir = StringUtil.nullOrEmptyString(archiveDirectoryName) ? LogManager.getArchiveDirectoryName() : archiveDirectoryName; archiveDirectory = new ConstrainedDirectory(archiveDir); int maxArchiveSizeMegaBytes = IntegerFormatter.getIntValue(maxArchiveSize); archiveDirectory.setMaxMegaBytes( maxArchiveSizeMegaBytes <= 0 ? DefaultLogMaxMegaBytes : maxArchiveSizeMegaBytes); monitorDirectories(); createLogFile(fileBaseName); if (getLayout() == null) { setLayout(new StandardLayout()); } super.activateOptions(); } /** * Override of org.apache.log4j.WriterAppender.reset() * Catch an IllegalStateException and keep going * instead of always failing during a reset(). */ protected void reset () { try { super.reset(); } catch (IllegalStateException e) { /* After some errors, the CharsetEncoder in the output stream gets into a state where a close always fails. Because log4j setFile() calls reset() which always tries to close the quiet writer (qw) and the close fails with illegal state, you can never set a new file and move on. So, we catch the illegal state exception and set the quiet writer instance to null, so processing can continue (e.g during archive log). */ this.qw = null; } } }