Package com.sun.enterprise.server.logging

Source Code of com.sun.enterprise.server.logging.FileandSyslogHandler$MeteredStream

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License").  You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code.  If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license."  If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above.  However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.server.logging;

import java.io.*;
import java.util.Date;
import java.util.LinkedList;
import java.util.ArrayList;
import java.text.MessageFormat;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.FieldPosition;
import java.util.logging.StreamHandler;
import java.util.logging.LogRecord;
import java.util.logging.Level;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;

import javax.management.ObjectName;
import javax.management.MBeanServer;

import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.server.ServerContext;

import com.sun.enterprise.server.logging.stats.ErrorStatistics;
import com.sun.enterprise.server.logging.logviewer.backend.LogFilter;
import com.sun.enterprise.server.logging.logviewer.backend.LogFile;

import com.sun.enterprise.config.serverbeans.LogService;

// Delete
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.Vector;

/**
* FileandSyslogHandler publishes formatted log Messages to a FILE.
*
* @AUTHOR: Hemanth Puttaswamy
* _REVISIT_:
* TODO:
* 1. Ability to lock log files to get exclusive write access
*/
public class FileandSyslogHandler extends StreamHandler {
    // This is a OutputStream to keep track of number of bytes
    // written out to the stream
    private MeteredStream meter;
    private final AMXLoggingHook     mAMXLoggingHook;

    private Date date = new Date( );
    private static final String LOGS_DIR = "logs";
   
    static final String LOG_FILENAME_PREFIX = "server";
    static final String LOG_FILENAME_SUFFIX = ".log";
   
    private static final String logFileName = LOG_FILENAME_PREFIX + LOG_FILENAME_SUFFIX;

    private String absoluteFileName = null;

    private static boolean isInitialized = false;

    private LogMBean logMBean;

    private LogService logService = null;

    private static final int WARNING = Level.WARNING.intValue();

    private static final int SEVERE = Level.SEVERE.intValue();

    private static final String INSTANCE_ROOT_PROPERTY =
    "com.sun.aas.instanceRoot";     

    private static final String LOGGING_MAX_HISTORY_FILES = "com.sun.enterprise.server.logging.max_history_files";

    // For now the mimimum rotation value is 0.5 MB.
    private static final int MINIMUM_FILE_ROTATION_VALUE = 500000;

    // Initially the LogRotation will be off until the domain.xml value is read.
    private int limitForFileRotation = 0;

    private final Object fileUpdateLock = new Object( );

    private boolean rotationInProgress = false;

    public static final FileandSyslogHandler thisInstance =
        new FileandSyslogHandler( );

    private final LinkedList pendingLogRecordList = new LinkedList();

    // Rotation can be done in 3 ways
    // 1. Based on the Size: Rotate when some Threshold number of bytes are
    //    written to server.log
    // 2. Based on the Time: Rotate ever 'n' minutes, mostly 24 hrs
    // 3. Rotate now
    // For mechanisms 2 and 3 we will use this flag. The rotate() will always
    // be fired from the publish( ) method for consistency
    private static boolean rotationRequested = false;

    // No apparent reason this should be 'synchronized', but perhaps there is a side effect
    // if it were removed.  Leaving it in for 9.1.1
    public static synchronized FileandSyslogHandler getInstance( ) {
        return thisInstance;
    }

    private static final String LOG_ROTATE_DATE_FORMAT =
        "yyyy-MM-dd'T'HH-mm-ss";

    private static final SimpleDateFormat logRotateDateFormatter =
        new SimpleDateFormat( LOG_ROTATE_DATE_FORMAT );
  
    // We maintain a list (Vector) of the last MAX_RECENT_ERRORS WARNING
    // or SEVERE error messages that were logged. The DAS (or any other
    // client) can then obtain these messages through the ServerRuntimeMBean
    // and determine if the server instance (or Node Agent) is in an
    // error state.
    private static Vector _recentErrors = new Vector();

    private static final int MAX_RECENT_ERRORS = 4;
   
    public synchronized static Vector getRecentErrorMessages() {
        return _recentErrors;
    }
       
    public synchronized static void clearRecentErrorMessages() {
        _recentErrors = new Vector();
    }
   
    private synchronized static void addRecentErrorMessage(String error) {
        if (_recentErrors.size() < MAX_RECENT_ERRORS) {
            _recentErrors.add(error);
        } else {
            _recentErrors.removeElementAt(0);
            _recentErrors.add(MAX_RECENT_ERRORS - 1, error);
        }
    }       

    /**
     *  This method is invoked from LogManager.reInitializeLoggers() to
     *  change the location of the file.
     */
    void changeFileName( String fileName ) {
        // If the file name is same as the current file name, there
        // is no need to change the filename
        if( fileName.trim().equals( absoluteFileName ) ) {
            return;
        }
        synchronized( this ) {
            super.flush( );
            super.close();
            try {
                openFile( fileName );
                // Change the file for LogViewer
                LogFilter.setLogFile( new LogFile( fileName ) );
                absoluteFileName = fileName;
            } catch( IOException ix ) {
                new ErrorManager().error(
                    "FATAL ERROR: COULD NOT OPEN LOG FILE. " +
                    "Please Check to make sure that the directory for " +
                    "Logfile exists. Currently reverting back to use the " +
                    " default server.log", ix, ErrorManager.OPEN_FAILURE );
                try {
                    // Reverting back to the old server.log
                    openFile( absoluteFileName );
                } catch( Exception e ) {
                    new ErrorManager().error(
                        "FATAL ERROR: COULD NOT RE-OPEN SERVER LOG FILE. ", e,
                        ErrorManager.OPEN_FAILURE );
                }
            }
        }
    }

    /**
     * A simple getter to access the complete fileName used by this FileHandler.
     */
    String getAbsoluteLogFileName( ) {
        return absoluteFileName;
    }

    /**
     *  A package private method to set the limit for File Rotation.
     */
    synchronized void setLimitForRotation( int rotationLimitInBytes ) {
        if ((rotationLimitInBytes == 0) ||
          (rotationLimitInBytes >= MINIMUM_FILE_ROTATION_VALUE )) {
            limitForFileRotation = rotationLimitInBytes;
        }else{
            new ErrorManager().error(
                "WARNING: Log Rotation Size Limits > 0 KB and < 500 KB default to 500 KB.",
                null,ErrorManager.GENERIC_FAILURE );
            limitForFileRotation = MINIMUM_FILE_ROTATION_VALUE;
        }       
    }
   


    // NOTE: This private class is copied from java.util.logging.FileHandler
    // A metered stream is a subclass of OutputStream that
    //   (a) forwards all its output to a target stream
    //   (b) keeps track of how many bytes have been written
    private class MeteredStream extends OutputStream {
        OutputStream out;
        long written;

        MeteredStream(OutputStream out, long written) {
            this.out = out;
            this.written = written;
        }

        public void write(int b) throws IOException {
            out.write(b);
            written++;
        }

        public void write(byte buff[]) throws IOException {
            out.write(buff);
            written += buff.length;
        }

        public void write(byte buff[], int off, int len) throws IOException {
            out.write(buff,off,len);
            written += len;
        }

        public void flush() throws IOException {
            out.flush();
        }

        public void close() throws IOException {
            out.close();
        }
    }


    /**
     * The Default constructor creates the File and sets the
     * UniformLogFormatter.
     */
    protected FileandSyslogHandler( ) {
        mAMXLoggingHook    = AMXLoggingHook.getInstance();
        try {
            if (!AsyncMemoryHandler.isEnabled())
                setFormatter( new UniformLogFormatter( ) );
            else {
                setFormatter( new CustomLogFormatter( ) );
            }
        } catch( Exception e ) {
            new ErrorManager().error(
                "FATAL ERROR: COULD NOT INSTANTIATE FILE AND SYSLOG HANDLER",
                e, ErrorManager.GENERIC_FAILURE );
        }
    }



    /**
     * Creates the File under the specified instance directory
     */
    public String createFileName( ) {
        if ( (absoluteFileName != null&& (! absoluteFileName.equals("")) )
            return getAbsoluteLogFileName();
        ServerContext sc = ApplicationServer.getServerContext();
        String instDir = "";
        if (sc != null) {
            instDir = sc.getInstanceEnvironment().getInstancesRoot();
        } else {
            instDir = System.getProperty( INSTANCE_ROOT_PROPERTY );
        }
        String[] names = {instDir, LOGS_DIR, getLogFileName() };
        // Create an absolute log filename
        return StringUtils.makeFilePath(names, false);
    }
        
    /**
     *  Creates the file and initialized MeteredStream and passes it on to
     *  Superclass (java.util.logging.StreamHandler).
     */
    private void openFile( String fileName ) throws IOException {
        File file = new File( fileName );
        FileOutputStream fout = new FileOutputStream( fileName, true );
        BufferedOutputStream bout = new BufferedOutputStream( fout );
        meter = new MeteredStream( bout, file.length() );
        setOutputStream( meter );
    }

    /**
     * Request Rotation called from Rotation Timer Task or LogMBean
     */
    void requestRotation( ) {
        synchronized( this ) {
            rotationRequested = true;
        }
    }

    /**
     * cleanup the history log file based on JVM system property "max_history_files".
     *
     * If it is defined with valid number, we only keep that number of history logfiles;
     * If "max_history_files" is defined without value, then default that number to be 10;
     * If "max_history_files" is defined with value 0, no histry log file will
     * be keeped; and server.log is the only log file.
     */
    public void cleanUpHistoryLogFiles() {
        String nStr = System.getProperty(LOGGING_MAX_HISTORY_FILES);
        if (nStr==null) return;

        int maxHistryFiles = 10;
        if (!"".equals(nStr)) {
            try {
                maxHistryFiles = Integer.parseInt(nStr);
            } catch (NumberFormatException e) {};
        }
        if (maxHistryFiles<0) return;

        File   dir  = new File(absoluteFileName).getParentFile();
        if (dir==null) return;

        File[]   fset = dir.listFiles();
        ArrayList candidates = new ArrayList();
        for (int i=0; fset!=null && i<fset.length; i++) {
            if ( !logFileName.equals(fset[i].getName()) &&
                 fset[i].isFile() &&
                 fset[i].getName().startsWith(logFileName) ) {
                 candidates.add(fset[i].getAbsolutePath() );
            }
        }
        if (candidates.size() <= maxHistryFiles) return;

        Object[] pathes = candidates.toArray();
        java.util.Arrays.sort(pathes);
        try {
            for (int i=0; i<pathes.length-maxHistryFiles; i++) {
    new File((String)pathes[i]).delete();
            }
        } catch (Exception e) {
            new ErrorManager().error("FATAL ERROR: COULD NOT DELETE LOG FILE..",
                                     e, ErrorManager.GENERIC_FAILURE );
        }
    }


    /**
     * A Simple rotate method to close the old file and start the new one
     * when the limit is reached.
     */
    private void rotate( ) {
        final FileandSyslogHandler thisInstance = this;
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Object run( ) {
                    thisInstance.flush( );
                    thisInstance.close();
                    StringBuffer renamedFileName = null;
                    try {
                        File oldFile = new File( absoluteFileName );
                        renamedFileName =
                            new StringBuffer( absoluteFileName + "_" );
                                logRotateDateFormatter.format(
                                    new Date(), renamedFileName,
                                    new FieldPosition( 0 ) );
                        File rotatedFile = new File(
                            renamedFileName.toString() );
                        boolean renameSuccess = false;
                        if( !renameSuccess ) {
                            // If we don't succeed with file rename which
                            // most likely can happen on Windows because
                            // of multiple file handles opened. We go through
                            // Plan B to copy bytes explicitly to a renamed
                            // file.
                            planBLogRotate(absoluteFileName,
                                renamedFileName.toString( ) );
                            String freshServerLogFileName = createFileName( );
                            // We do this to make sure that server.log
                            // contents are flushed out to start from a
                            // clean file again after the rename..
                            FileOutputStream fo =
                                new FileOutputStream( freshServerLogFileName );
                            fo.close( );
                        }
                        openFile( createFileName( ) );
                        LogFilter.setLogFile( new LogFile( absoluteFileName) );
                        // This will ensure that the log rotation timer
                        // will be restarted if there is a value set
                        // for time based log rotation
                        LogRotationTimer.getInstance( ).restartTimer( );

                        cleanUpHistoryLogFiles();
                    } catch( IOException ix ) {
                        new ErrorManager().error(
                            "FATAL ERROR: COULD NOT OPEN LOG FILE..", ix,
                            ErrorManager.OPEN_FAILURE );
                    }
                    return null;
                }
            }
        );
    }


    /**
     *  This method is solely provided as a plan B for Windows. On Windows
     *  the file rename fails if there are open handles to the file.
     *  We want to make sure that the log rotation succeeds in this case also.
     *  Basically we copy the contents of the file instead of renaming.
     */
    private void planBLogRotate( String originalFileName,
        String renamedFileName )
    {
        FileOutputStream fo = null;
        FileInputStream fi = null;
        try {
            fo = new FileOutputStream( renamedFileName );
            fi = new FileInputStream( originalFileName );

            int BUF_SIZE = 4096;

            byte[] buffer = new byte[BUF_SIZE];
            int i = -1;
            do {
                i = fi.read( buffer );
                // Reached EOF.
                if( i == -1 ) { break; }
                if( i == BUF_SIZE ) {
                    fo.write( buffer );
                } else {
                    // We have less number of bytes to read than the BUF_SIZE
                    // So, just create a temporary buffer with the exact
                    // number of bytes read and write that to the rotated
                    // file.
                    byte[] tempBuffer = new byte[i];
                    int j = 0;
                    for( j = 0; j < i; j++ ) {
                        tempBuffer[j] = buffer[j];
                    }
                    fo.write( tempBuffer );
                }
            } while( true );
        } catch( Exception e ) {
           // If we fail in Plan B. Too bad, we should not
           // log a message here as it may lead to recursion.
        } finally {
            try {
                fo.close( );
                fi.close( );
            } catch( Exception e ) { }
        }
    }


    /**
     * Publishes the logrecord using the super class and checks to see
     * if a file rotation is required.
     */
    public synchronized void publish( LogRecord record ) {
        // This is provided to avoid the recursion. When Log Rotation
        // is called, there is a chance that any of the called routines
        // in the Log Rotation call path may log a message, which may
        // result in a recursion. Hence, we shall log those log messages
        // after the rotation is completed, so as to avoid the recursion.
        if( rotationInProgress ) {
      pendingLogRecordList.addLast(record);
      return;
  }
        // Important: There are issues with double instantiation of
        // FileandSyslogHandler if we move the createFile/openFile calls
        // to the constructor. For now, we will do this for the first
        // publish call.
        if( meter == null ) {
            try {
                absoluteFileName = createFileName( );
                openFile( absoluteFileName );
            } catch( Exception e ) {
                throw new RuntimeException(
                    "Serious Error Couldn't open Log File" + e );
            }
        }
        super.publish( record )
        flush( );
        if ( ( rotationRequested
        || ( ( limitForFileRotation > 0 )
        &&  ( meter.written >= limitForFileRotation ) ) ) 
        {
            rotationInProgress = true
            // If we have written more than the limit set for the
            // file, or rotation requested from the Timer Task or LogMBean
            // start fresh with a new file after renaming the old file.
            rotate( );
            rotationInProgress = false;
            rotationRequested = false;
      while (pendingLogRecordList.size() != 0) {
    publish((LogRecord) pendingLogRecordList.removeFirst());
      }
        }
       
        if (mAMXLoggingHook != null) {
            mAMXLoggingHook.publish( record, getFormatter() );
        }
       
        int level = record.getLevel( ).intValue();
        // The processing here is to raise alarms if
        // alarm flag in domain.xml is on and the log level is SEVERE or
        // WARNING
        if(( level != WARNING ) && (level != SEVERE)) {
            return;
        }
       
        //Any WARNING or SEVERE message is placed onto a singleton rolling
        // error message list. This can be queried as part of a running
        // server's status. This allows the last N error messages to be easily
        // viewed without having to consult the log file.
        String logMessage = getFormatter().format(record);
        addRecentErrorMessage(logMessage);
        ErrorStatistics.singleton().updateStatistics(record); // error stats.
       
        if( logService == null ) {
            logService = ServerLogManager.getLogService( );
        }
        // _REVISIT_: Deprecate Alarms flag
        if( logService == null ) return;
      
        // Raise an alarm if the logged message is a SEVERE or WARNING
        // message.
        if( record.getLevel( ).intValue() == WARNING ) {
            LogMBean.getInstance().raiseWarningAlarm( record );
        } else if( record.getLevel( ).intValue() == SEVERE ) {
            LogMBean.getInstance().raiseSevereAlarm( record );
        }
    }
   
    /**
     *Returns the AMXLoggingHook to use for this logger.
     *<p>
     *This method is here primarily so a subclass can override it if needed.
     *@return AMXLoggingHook to use
     */
    protected AMXLoggingHook createAMXLoggingHook() {
        return new AMXLoggingHook();
    }
   
    protected String getLogFileName() {
        return logFileName;
    }
}
TOP

Related Classes of com.sun.enterprise.server.logging.FileandSyslogHandler$MeteredStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.