From d755882f59202fe62b2ad5a141b3c044c1898aa6 Mon Sep 17 00:00:00 2001
From: boli <boli@localhost>
Date: Thu, 03 May 2007 21:55:23 +0000
Subject: [PATCH] Major changes made to the logging framework. It should resolve the following issues:
---
opends/src/server/org/opends/server/loggers/MultifileTextWriter.java | 615 +++++++++++++++++++++++++++++++++++++------------------
1 files changed, 412 insertions(+), 203 deletions(-)
diff --git a/opends/src/server/org/opends/server/loggers/MultifileTextWriter.java b/opends/src/server/org/opends/server/loggers/MultifileTextWriter.java
index c84025a..5ee2004 100644
--- a/opends/src/server/org/opends/server/loggers/MultifileTextWriter.java
+++ b/opends/src/server/org/opends/server/loggers/MultifileTextWriter.java
@@ -29,21 +29,20 @@
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.DebugLogLevel;
-import org.opends.server.config.ConfigAttribute;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.InvokableMethod;
-import org.opends.server.types.ResultCode;
+import org.opends.server.types.*;
+import org.opends.server.types.FilePermission;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.debugVerbose;
import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
-import static org.opends.server.messages.ConfigMessages.*;
-import static org.opends.server.messages.MessageHandler.getMessage;
+import org.opends.server.admin.std.server.SizeLimitLogRotationPolicyCfg;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.util.TimeThread;
import java.io.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.ArrayList;
+import java.util.List;
/**
* A MultiFileTextWriter is a specialized TextWriter which supports publishing
@@ -54,15 +53,20 @@
* When a switch is required, the writer closes the current file and opens a
* new one named in accordance with a specified FileNamingPolicy.
*/
-public class MultifileTextWriter extends TextWriter
- implements ServerShutdownListener
+public class MultifileTextWriter
+ implements ServerShutdownListener, TextWriter,
+ ConfigurationChangeListener<SizeLimitLogRotationPolicyCfg>
{
private static final String UTF8_ENCODING= "UTF-8";
- private static final int BUFFER_SIZE= 65536;
- private CopyOnWriteArrayList<RotationPolicy> rotationPolicies;
- private CopyOnWriteArrayList<RetentionPolicy> retentionPolicies;
+ private CopyOnWriteArrayList<RotationPolicy> rotationPolicies =
+ new CopyOnWriteArrayList<RotationPolicy>();
+ private CopyOnWriteArrayList<RetentionPolicy> retentionPolicies =
+ new CopyOnWriteArrayList<RetentionPolicy>();
+
private FileNamingPolicy namingPolicy;
+ private FilePermission filePermissions;
+ private LogPublisherErrorHandler errorHandler;
//TODO: Implement actions.
private ArrayList<ActionType> actions;
@@ -71,82 +75,22 @@
private int bufferSize;
private boolean autoFlush;
private boolean append;
- private int interval;
+ private long interval;
private boolean stopRequested;
+ private long sizeLimit = 0;
private Thread rotaterThread;
- /**
- * Get the writer for the initial log file and initialize the
- * rotation policy.
- * @param naming - the file naming policy in use
- * @param encoding - the encoding to use when writing log records.
- * @param autoFlush - indicates whether the file should be flushed
- * after every record written.
- * @param append - indicates whether to append to the existing file or to
- * overwrite it.
- * @param bufferSize - the buffer size to use for the writer.
- * @return a PrintWriter for the initial log file
- * @throws IOException if the initial log file could not be opened
- */
- private static PrintWriter getInitialWriter(FileNamingPolicy naming,
- String encoding,
- boolean autoFlush,
- boolean append,
- int bufferSize)
- throws IOException
- {
- File file = naming.getInitialName();
- return constructWriter(file, encoding, autoFlush, append, bufferSize);
- }
+ private long lastRotationTime = TimeThread.getTime();
+ private long lastCleanTime = TimeThread.getTime();
+ private long lastCleanCount = 0;
+ private long totalFilesRotated = 0;
+ private long totalFilesCleaned = 0;
- /**
- * Construct a PrintWriter for a file.
- * @param file - the file to open for writing
- * @param encoding - the encoding to use when writing log records.
- * @param autoFlush - indicates whether the file should be flushed
- * after every record written.
- * @param append - indicates whether the file should be appended to or
- * truncated.
- * @param bufferSize - the buffer size to use for the writer.
- * @return a PrintWriter for the specified file.
- * @throws IOException if the PrintWriter could not be constructed
- * or if the file already exists and it was indicated this should be
- * an error.
- */
- private static PrintWriter constructWriter(File file, String encoding,
- boolean autoFlush, boolean append,
- int bufferSize)
- throws IOException
- {
- FileOutputStream fos= new FileOutputStream(file, append);
- OutputStreamWriter osw= new OutputStreamWriter(fos, encoding);
- BufferedWriter bw = null;
- if(bufferSize <= 0)
- {
- bw= new BufferedWriter(osw);
- }
- else
- {
- bw= new BufferedWriter(osw, bufferSize);
- }
- return new PrintWriter(bw, autoFlush);
- }
-
- /**
- * Creates a new instance of MultiFileTextWriter with the supplied policies.
- *
- * @param name the name of the log rotation thread.
- * @param namingPolicy the file naming policy to use to name rotated log
- * files.
- * @throws IOException if an error occurs while creating the log file.
- */
- public MultifileTextWriter(String name, FileNamingPolicy namingPolicy)
- throws IOException
- {
- this(name, 5000, namingPolicy, UTF8_ENCODING,
- true, true, BUFFER_SIZE, null, null);
- }
+ /** The underlying output stream. */
+ private MeteredStream outputStream;
+ /** The underlaying buffered writer using the output steram. */
+ private BufferedWriter writer;
/**
* Creates a new instance of MultiFileTextWriter with the supplied policies.
@@ -155,54 +99,264 @@
* @param interval the interval to check whether the logs need to be rotated.
* @param namingPolicy the file naming policy to use to name rotated log.
* files.
+ * @param filePermissions the file permissions to set on the log files.
+ * @param errorHandler the log publisher error handler to notify when
+ * an error occurs.
* @param encoding the encoding to use to write the log files.
* @param autoFlush whether to flush the writer on every println.
* @param append whether to append to an existing log file.
* @param bufferSize the bufferSize to use for the writer.
- * @param rotationPolicies the rotation policy to use for log rotation.
- * @param retentionPolicies the retention policy to use for log rotation.
* @throws IOException if an error occurs while creating the log file.
+ * @throws DirectoryException if an error occurs while preping the new log
+ * file.
*/
- public MultifileTextWriter(String name, int interval,
- FileNamingPolicy namingPolicy, String encoding,
- boolean autoFlush, boolean append, int bufferSize,
- CopyOnWriteArrayList<RotationPolicy> rotationPolicies,
- CopyOnWriteArrayList<RetentionPolicy> retentionPolicies)
- throws IOException
+ public MultifileTextWriter(String name, long interval,
+ FileNamingPolicy namingPolicy,
+ FilePermission filePermissions,
+ LogPublisherErrorHandler errorHandler,
+ String encoding,
+ boolean autoFlush,
+ boolean append,
+ int bufferSize)
+ throws IOException, DirectoryException
{
- super(getInitialWriter(namingPolicy, encoding,
- autoFlush, append, bufferSize), true);
+ File file = namingPolicy.getInitialName();
+ constructWriter(file, filePermissions, encoding, append,
+ bufferSize);
+
this.name = name;
this.interval = interval;
this.namingPolicy = namingPolicy;
- this.rotationPolicies = rotationPolicies;
- this.retentionPolicies = retentionPolicies;
+ this.filePermissions = filePermissions;
+ this.errorHandler = errorHandler;
- this.encoding = encoding;
+ this.encoding = UTF8_ENCODING;
this.autoFlush = autoFlush;
this.append = append;
this.bufferSize = bufferSize;
this.stopRequested = false;
- // We will lazily launch the rotaterThread
- // to ensure initialization safety.
+ rotaterThread = new RotaterThread(this);
+ rotaterThread.start();
DirectoryServer.registerShutdownListener(this);
}
/**
+ * Construct a PrintWriter for a file.
+ * @param file - the file to open for writing
+ * @param filePermissions - the file permissions to set on the file.
+ * @param encoding - the encoding to use when writing log records.
+ * @param append - indicates whether the file should be appended to or
+ * truncated.
+ * @param bufferSize - the buffer size to use for the writer.
+ * @throws IOException if the PrintWriter could not be constructed
+ * or if the file already exists and it was indicated this should be
+ * an error.
+ * @throws DirectoryException if there was a problem setting permissions on
+ * the file.
+ */
+ private void constructWriter(File file, FilePermission filePermissions,
+ String encoding, boolean append,
+ int bufferSize)
+ throws IOException, DirectoryException
+ {
+ // Create new file if it doesn't exist
+ if(!file.exists())
+ {
+ file.createNewFile();
+ }
+
+ FileOutputStream stream = new FileOutputStream(file, append);
+ outputStream = new MeteredStream(stream, 0);
+
+ OutputStreamWriter osw = new OutputStreamWriter(outputStream, encoding);
+ BufferedWriter bw = null;
+ if(bufferSize <= 0)
+ {
+ writer = new BufferedWriter(osw);
+ }
+ else
+ {
+ writer = new BufferedWriter(osw, bufferSize);
+ }
+
+ if(FilePermission.canSetPermissions())
+ {
+ FilePermission.setPermissions(file, filePermissions);
+ }
+ }
+
+
+ /**
+ * Add a rotation policy to enforce on the files written by this writer.
+ *
+ * @param policy The rotation policy to add.
+ */
+ public void addRotationPolicy(RotationPolicy policy)
+ {
+ this.rotationPolicies.add(policy);
+
+ if(policy instanceof SizeBasedRotationPolicy)
+ {
+ SizeBasedRotationPolicy sizePolicy = ((SizeBasedRotationPolicy)policy);
+ if(sizeLimit == 0 ||
+ sizeLimit > sizePolicy.currentConfig.getFileSizeLimit())
+ {
+ sizeLimit = sizePolicy.currentConfig.getFileSizeLimit();
+ }
+ // Add this as a change listener so we can update the size limit.
+ sizePolicy.currentConfig.addSizeLimitChangeListener(this);
+ }
+ }
+
+ /**
+ * Add a retention policy to enforce on the files written by this writer.
+ *
+ * @param policy The retention policy to add.
+ */
+ public void addRetentionPolicy(RetentionPolicy policy)
+ {
+ this.retentionPolicies.add(policy);
+ }
+
+ /**
+ * Removes all the rotation policies currently enforced by this writer.
+ */
+ public void removeAllRotationPolicies()
+ {
+ for(RotationPolicy policy : rotationPolicies)
+ {
+ if(policy instanceof SizeBasedRotationPolicy)
+ {
+ sizeLimit = 0;
+
+ // Remove this as a change listener.
+ SizeBasedRotationPolicy sizePolicy = ((SizeBasedRotationPolicy)policy);
+ sizePolicy.currentConfig.removeSizeLimitChangeListener(this);
+ }
+ }
+ }
+
+ /**
+ * Removes all retention policies being enforced by this writer.
+ */
+ public void removeAllRetentionPolicies()
+ {
+ this.retentionPolicies.clear();
+ }
+
+ /**
+ * Set the auto flush setting for this writer.
+ *
+ * @param autoFlush If the writer should flush the buffer after every line.
+ */
+ public void setAutoFlush(boolean autoFlush)
+ {
+ this.autoFlush = autoFlush;
+ }
+
+ /**
+ * Set the append setting for this writter.
+ *
+ * @param append If the writer should append to an existing file.
+ */
+ public void setAppend(boolean append)
+ {
+ this.append = append;
+ }
+
+ /**
+ * Set the buffer size for this writter.
+ *
+ * @param bufferSize The size of the underlying output stream buffer.
+ */
+ public void setBufferSize(int bufferSize)
+ {
+ this.bufferSize = bufferSize;
+ }
+
+ /**
+ * Set the file permission to set for newly created log files.
+ *
+ * @param filePermissions The file permission to set for new log files.
+ */
+ public void setFilePermissions(FilePermission filePermissions)
+ {
+ this.filePermissions = filePermissions;
+ }
+
+ /**
+ * Retrieves the current naming policy used to generate log file names.
+ *
+ * @return The current naming policy in use.
+ */
+ public FileNamingPolicy getNamingPolicy()
+ {
+ return namingPolicy;
+ }
+
+ /**
+ * Set the naming policy to use when generating new log files.
+ *
+ * @param namingPolicy the naming policy to use to name log files.
+ */
+ public void setNamingPolicy(FileNamingPolicy namingPolicy)
+ {
+ this.namingPolicy = namingPolicy;
+ }
+
+ /**
+ * Set the internval in which the rotator thread checks to see if the log
+ * file should be rotated.
+ *
+ * @param interval The interval to check if the log file needs to be rotated.
+ */
+ public void setInterval(long interval)
+ {
+ this.interval = interval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isConfigurationChangeAcceptable(
+ SizeLimitLogRotationPolicyCfg config, List<String> unacceptableReasons)
+ {
+ // This should always be ok
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ConfigChangeResult applyConfigurationChange(
+ SizeLimitLogRotationPolicyCfg config)
+ {
+ if(sizeLimit == 0 || sizeLimit > config.getFileSizeLimit())
+ {
+ sizeLimit = config.getFileSizeLimit();
+ }
+
+ return new ConfigChangeResult(ResultCode.SUCCESS, false,
+ new ArrayList<String>());
+ }
+
+ /**
* A rotater thread is responsible for checking if the log files need to be
* rotated based on the policies. It will do so if necessary.
*/
private class RotaterThread extends DirectoryThread
{
+ MultifileTextWriter writer;
/**
* Create a new rotater thread.
*/
- public RotaterThread()
+ public RotaterThread(MultifileTextWriter writer)
{
super(name);
+ this.writer = writer;
}
/**
@@ -229,37 +383,28 @@
}
}
- if(rotationPolicies != null)
+ for(RotationPolicy rotationPolicy : rotationPolicies)
{
- for(RotationPolicy rotationPolicy : rotationPolicies)
+ if(rotationPolicy.rotateFile(writer))
{
- if(rotationPolicy.rotateFile())
- {
- try
- {
- rotate();
- }
- catch (IOException ioe)
- {
- //TODO: Comment this after AOP logging is complete.
- //int msgID = MSGID_CONFIG_LOGGER_ROTATE_FAILED;
- //Error.logError(ErrorLogCategory.CORE_SERVER,
- // ErrorLogSeverity.SEVERE_ERROR, msgID, ioe);
- }
- }
+ rotate();
}
}
- if(retentionPolicies != null)
+ for(RetentionPolicy retentionPolicy : retentionPolicies)
{
- for(RetentionPolicy retentionPolicy : retentionPolicies)
+ int numFilesDeleted =
+ retentionPolicy.deleteFiles(writer);
+ if(numFilesDeleted > 0)
{
- int numFilesDeleted = retentionPolicy.deleteFiles();
- if (debugEnabled())
- {
- debugVerbose("%d files deleted by rentention policy",
- numFilesDeleted);
- }
+ lastCleanTime = TimeThread.getTime();
+ lastCleanCount = numFilesDeleted;
+ totalFilesCleaned++;
+ }
+ if (debugEnabled())
+ {
+ debugVerbose("%d files deleted by rentention policy",
+ numFilesDeleted);
}
}
}
@@ -285,11 +430,13 @@
*/
public void processServerShutdown(String reason)
{
- startShutDown();
+ stopRequested = true;
// Wait for rotater to terminate
while (rotaterThread != null && rotaterThread.isAlive()) {
try {
+ // Interrupt if its sleeping
+ rotaterThread.interrupt();
rotaterThread.join();
}
catch (InterruptedException ex) {
@@ -297,9 +444,14 @@
}
}
- writer.flush();
- writer.close();
- writer = null;
+ DirectoryServer.deregisterShutdownListener(this);
+
+ removeAllRotationPolicies();
+ removeAllRetentionPolicies();
+
+ // Don't close the writer as there might still be message to be
+ // written. manually shutdown just before the server process
+ // exists.
}
/**
@@ -307,27 +459,27 @@
*
* @return if the publish is in shutdown mode.
*/
- private synchronized boolean isShuttingDown()
+ private boolean isShuttingDown()
{
return stopRequested;
}
/**
- * Tell the writer to start shutting down.
- */
- private synchronized void startShutDown()
- {
- stopRequested = true;
- }
-
- /**
* Shutdown the text writer.
*/
public void shutdown()
{
processServerShutdown(null);
- DirectoryServer.deregisterShutdownListener(this);
+ try
+ {
+ writer.flush();
+ writer.close();
+ }
+ catch(Exception e)
+ {
+ errorHandler.handleCloseError(e);
+ }
}
@@ -336,93 +488,84 @@
*
* @param record the log record to write.
*/
- public synchronized void writeRecord(String record)
+ public void writeRecord(String record)
{
- // Launch writer rotaterThread if not running
- if (rotaterThread == null) {
- rotaterThread = new RotaterThread();
- rotaterThread.start();
+ synchronized(this)
+ {
+ try
+ {
+ writer.write(record);
+ writer.newLine();
+ }
+ catch(Exception e)
+ {
+ errorHandler.handleWriteError(record, e);
+ }
+
+ if(autoFlush)
+ {
+ flush();
+ }
}
- writer.println(record);
+ if(sizeLimit > 0 && outputStream.written >= sizeLimit)
+ {
+ rotate();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void flush()
+ {
+ try
+ {
+ writer.flush();
+ }
+ catch(Exception e)
+ {
+ errorHandler.handleFlushError(e);
+ }
}
/**
* Tries to rotate the log files. If the new log file alreadly exists, it
* tries to rename the file. On failure, all subsequent log write requests
* will throw exceptions.
- *
- * @throws IOException if an error occurs while rotation the log files.
*/
- public void rotate() throws IOException
+ public synchronized void rotate()
{
- writer.flush();
- writer.close();
- writer = null;
+ try
+ {
+ writer.flush();
+ writer.close();
+ }
+ catch(Exception e)
+ {
+ errorHandler.handleCloseError(e);
+ }
File currentFile = namingPolicy.getInitialName();
File newFile = namingPolicy.getNextName();
currentFile.renameTo(newFile);
- writer = constructWriter(currentFile, encoding,
- autoFlush, append, bufferSize);
+ try
+ {
+ constructWriter(currentFile, filePermissions, encoding, append,
+ bufferSize);
+ }
+ catch (Exception e)
+ {
+ errorHandler.handleOpenError(currentFile, e);
+ }
//RotationActionThread rotThread =
// new RotationActionThread(newFile, actions, configEntry);
//rotThread.start();
- }
- /**
- * Invokes the specified method with the provided arguments.
- *
- * @param methodName The name of the method to invoke.
- * @param arguments The set of configuration attributes holding the
- * arguments to use for the method.
- *
- * @return The return value for the method, or <CODE>null</CODE> if it did
- * not return a value.
- *
- * @throws org.opends.server.types.DirectoryException
- * If there was no such method, or if an error occurred while attempting
- * to invoke it.
- */
- public Object invokeMethod(String methodName, ConfigAttribute[] arguments)
- throws DirectoryException
- {
- if(!methodName.equals("rotateNow"))
- {
- int msgID = MSGID_CONFIG_JMX_NO_METHOD;
- throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
- getMessage(msgID), msgID);
- }
-
- try
- {
- rotate();
- }
- catch(Exception e)
- {
- //TODO: Comment when AOP logging framework is complete.
- //int msgID = MSGID_CONFIG_LOGGER_ROTATE_FAILED;
- //throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
- // getMessage(msgID, e), msgID);
- }
-
- return null;
- }
-
- /**
- * Retrieves a list of the methods that may be invoked for this component.
- *
- * @return A list of the methods that may be invoked for this component.
- */
- public InvokableMethod[] getOperationSignatures()
- {
- InvokableMethod[] methods = new InvokableMethod[1];
- methods[0] = new InvokableMethod("rotateNow",
- "Rotate the log file immediately",
- null, "void", true, true);
- return methods;
+ totalFilesRotated++;
+ lastRotationTime = TimeThread.getTime();
}
/**
@@ -434,4 +577,70 @@
{
this.actions = actions;
}
+
+ /**
+ * Retrieves the number of bytes written to the current log file.
+ *
+ * @return The number of bytes written to the current log file.
+ */
+ public long getBytesWritten()
+ {
+ return outputStream.written;
+ }
+
+ /**
+ * Retrieves the last time one or more log files are cleaned in this instance
+ * of the Directory Server. If log files have never been cleaned, this value
+ * will be the time the server started.
+ *
+ * @return The last time log files are cleaned.
+ */
+ public long getLastCleanTime()
+ {
+ return lastCleanTime;
+ }
+
+ /**
+ * Retrieves the number of files cleaned in the last cleanup run.
+ *
+ * @return The number of files cleaned int he last cleanup run.
+ */
+ public long getLastCleanCount()
+ {
+ return lastCleanCount;
+ }
+
+ /**
+ * Retrieves the last time a log file was rotated in this instance of
+ * Directory Server. If a log rotation never
+ * occured, this value will be the time the server started.
+ *
+ * @return The last time log rotation occured.
+ */
+ public long getLastRotationTime()
+ {
+ return lastRotationTime;
+ }
+
+ /**
+ * Retrieves the total number file rotations occured in this instance of the
+ * Directory Server.
+ *
+ * @return The total number of file rotations.
+ */
+ public long getTotalFilesRotated()
+ {
+ return totalFilesRotated;
+ }
+
+ /**
+ * Retrieves teh total number of files cleaned in this instance of the
+ * Directory Server.
+ *
+ * @return The total number of files cleaned.
+ */
+ public long getTotalFilesCleaned()
+ {
+ return totalFilesCleaned;
+ }
}
--
Gitblit v1.10.0