/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.loggers; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.logging.ErrorManager; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import org.opends.server.api.InvokableComponent; import org.opends.server.core.DirectoryException; import org.opends.server.config.ConfigAttribute; import org.opends.server.config.ConfigEntry; import org.opends.server.types.DN; import org.opends.server.types.InvokableMethod; import org.opends.server.types.ResultCode; import org.opends.server.util.TimeThread; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.messages.ConfigMessages.*; import static org.opends.server.messages.MessageHandler.*; /** * Simple file logging Handler. * The DirectoryFileHandler can write to a specified file, * and can handle rotating the file based on predefined policies. */ public class DirectoryFileHandler extends Handler implements LoggerAlarmHandler, InvokableComponent { private Writer writer; private MeteredStream meter; private boolean append; private String filename; private int bufferSize = 65536; private long limit = 0; private File file; private ArrayList actions; private ConfigEntry configEntry; /** * Initialize a DirectoryFileHandler to write to the given filename. * * @param configEntry The configuration entry for the associated logger. * @param filename the name of the output file. * @param bufferSize the buffer size before flushing data to the file. * @exception IOException if there are IO problems opening the files. * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ public DirectoryFileHandler(ConfigEntry configEntry, String filename, int bufferSize) throws IOException, SecurityException { this.configEntry = configEntry; this.bufferSize = bufferSize; configure(); this.filename = filename; openFile(); } /** * Initialize a DirectoryFileHandler to write to the given filename, * with optional append. * * @param configEntry The configuration entry for the associated logger. * @param filename the name of the output file * @param append specifies append mode * @param bufferSize the buffer size before flushing data to the file. * @exception IOException if there are IO problems opening the files. * @exception SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ public DirectoryFileHandler(ConfigEntry configEntry, String filename, boolean append, int bufferSize) throws IOException, SecurityException { this.configEntry = configEntry; this.bufferSize = bufferSize; configure(); this.filename = filename; this.append = append; openFile(); } /** * Set the maximum file size limit. * * @param limit The maximum file size. */ public void setFileSize(long limit) { this.limit = limit; } /** * Private method to open the set of output files, based on the * configured instance variables. * * @exception IOException If there was an error while opening the file. * @exception SecurityException If a security manager exists and if * the caller does not have LoggingPermission. */ private void openFile() throws IOException, SecurityException { // We register our own ErrorManager during initialization // so we can record exceptions. InitializationErrorManager em = new InitializationErrorManager(); setErrorManager(em); file = new File(filename); // Create the initial log file. if (append) { open(file, true); } else { // FIXME - Should we rotate? open(file, false); } // Did we detect any exceptions during initialization? Exception ex = em.lastException; if (ex != null) { if (ex instanceof IOException) { throw (IOException) ex; } else if (ex instanceof SecurityException) { throw (SecurityException) ex; } else { throw new IOException("Exception: " + ex); } } // Install the normal default ErrorManager. setErrorManager(new ErrorManager()); } /** * Rotate the current file to the specified new file name. * @param newFile The name of the new file to rotate to. */ public void rotate(String newFile) { close(); File f1 = file; File f2 = new File(newFile); if (f1.exists()) { if (f2.exists()) { System.err.println("File:" + f2 + " already exists. Renaming..."); File f3 = new File(newFile + ".sav"); f2.renameTo(f3); } f1.renameTo(f2); } try { open(file, false); } catch (IOException ix) { // We don't want to throw an exception here, but we // report the exception to any registered ErrorManager. reportError(null, ix, ErrorManager.OPEN_FAILURE); } } /** * Format and publish a LogRecord. * * @param record Description of the log event. A null record is * silently ignored and is not published. * */ public void publish(LogRecord record) { String msg; try { msg = getFormatter().format(record); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); return; } synchronized(this) { try { writer.write(msg); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); return; } if(limit > 0 && meter.written >= limit) { rollover(); } } } /** * Return the number of bytes written to the current file. * * @return The number of bytes written to the current file. */ public long getFileSize() { return meter.written; } /** * This method is called from by the logger thread when a * file rotation needs to happen. */ public void rollover() { String newfilename = filename + "." + getFileExtension(); rotate(newfilename); RotationActionThread rotThread = new RotationActionThread(newfilename, actions, configEntry); rotThread.start(); } /** * This method sets the actions that need to be executed after rotation. * * @param actions An array of actions that need to be executed on rotation. */ public void setPostRotationActions(ArrayList actions) { this.actions = actions; } /** * Retrieves the DN of the configuration entry with which this component is * associated. * * @return The DN of the configuration entry with which this component is * associated. */ public DN getInvokableComponentEntryDN() { return configEntry.getDN(); } /** * 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; } /** * 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 null if it did * not return a value. * * @throws 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); } rollover(); return null; } /** * Close the current output stream. * */ public void close() { flushAndClose(); } /** * Return the extension for the target filename for the rotated file. * * @return The extension for the target filename for the rotated file. */ private String getFileExtension() { return TimeThread.getUTCTime(); } /** * Open the file and set the appropriate output stream. * * @param fname The path and name of the file to be written. * @param append Indicates whether to append to the existing file or to * overwrite it. * * @throws IOException If a problem occurs while opening the file. */ private void open(File fname, boolean append) throws IOException { long len = 0; if (append) { len = fname.length(); } FileOutputStream fout = new FileOutputStream(fname, append); BufferedOutputStream bout = null; if(bufferSize <= 0) { bout = new BufferedOutputStream(fout); } else { bout = new BufferedOutputStream(fout, bufferSize); } meter = new MeteredStream(bout, len); // flushAndClose(); writer = new BufferedWriter(new OutputStreamWriter(meter)); } /** * Private method to configure a DirectoryFileHandler * with default values. */ private void configure() { setLevel(Level.ALL); this.append = true; } /** * Flush any buffered messages and close the output stream. */ private void flushAndClose() { if (writer != null) { try { writer.flush(); writer.close(); } catch (Exception ex) { // We don't want to throw an exception here, but we // report the exception to any registered ErrorManager. reportError(null, ex, ErrorManager.CLOSE_FAILURE); } writer = null; } } /** * Flush any buffered messages. */ public void flush() { if (writer != null) { try { writer.flush(); } catch (Exception ex) { // We don't want to throw an exception here, but we // report the exception to any registered ErrorManager. reportError(null, ex, ErrorManager.FLUSH_FAILURE); } } } }