Package KFM.log

Source Code of KFM.log.KFMLog

/*
*  This software and supporting documentation were developed by
*
*    Siemens Corporate Technology
*    Competence Center Knowledge Management and Business Transformation
*    D-81730 Munich, Germany
*
*    Authors (representing a really great team ;-) )
*            Stefan B. Augustin, Thorbj�rn Hansen, Manfred Langen
*
*  This software is Open Source under GNU General Public License (GPL).
*  Read the text of this license in LICENSE.TXT
*  or look at www.opensource.org/licenses/
*
*  Once more we emphasize, that:
*  THIS SOFTWARE IS MADE AVAILABLE,  AS IS,  WITHOUT ANY WARRANTY
*  REGARDING  THE  SOFTWARE,  ITS  PERFORMANCE OR
*  FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER DISEASES OR
*  ITS CONFORMITY TO ANY SPECIFICATION. THE ENTIRE RISK AS TO QUALITY AND
*  PERFORMANCE OF THE SOFTWARE IS WITH THE USER.
*
*/


// KFMLog

// ************ package ******************************************************
package KFM.log;

// ************ imports ******************************************************
import KFM.DateTimeServices.*;
import KFM.Converter;
import KFM.File.*;
import KFM.Exceptions.*;
import java.io.*;
import java.util.*;
import KFM.Smtp;


/** Provide logging for servlets and library classes.
*
* <p>Every servlet has a member variable mLog of class KFMLog for writing logging
* information. LogFileName, LogFileLevel and LogFileOriginator should be configured
* in the servlet config file, f.e.:
* LogFileName=o:/KFM/logs/SieCD2.log
* LogFileLevel=2
* LogFileOriginator=SieCD2&#64;dampfer</p>
*
* <p>Currently four levels are supported: error (0), info (1), detail (3), debug (4)</p>
*
* <p>Logging information can be written by invoking mLog.xxx, where xxx can be one of the
* methods error, info, detail, debug, f.e.:<br>
* <code>mLog.error("Log application log and default log", new ProgrammerException("TEST"));</code></p>
*
* <p>If the invoking level is equal or lower the current logfile level then the information
* will be written to the log file.</p>
*
* <H2>Secondary logs</H2>
*
* <p>For test cases it is possible to write log information in two different log files.
* Let us say that you want to debug a DB error in Servlet Ex3. Let us say that Ex3 has 73456
* different log entries that keep confusing you. So it would be handy to have a log file
* that only contains the parts that you are interested in. You can use secondary logs to
* do that: log all relevant database information in a file DBEx3.log and also keep it in
* the servlet log file Ex3.log (mLog). Code it like this:</p>
*
* <pre>
*   KFMLog dblog = KFMLog.getInstance(
*     "DBEx3",                          //Originator
*     "o:/KFM/logs/DBEx3.log",          //Logfile
*     KFMLog.DETAIL_LEVEL,              //Detaillevel
*     mLog,                             //Also log to
*     "x,y@abc.de",                     //AdminEmail
*     "mail.host.de");                  //MailHost
* </pre>
*
*/
public class KFMLog {

    // ************************************************************
    // Inner classes
    // ************************************************************

    /**
     * <P> An inner class to encapsulate a log message repeated several times within some time span.
     * Objects of this class remember
     * <OL>
     *   <LI> the log message (a String) </LI>
     *   <LI> the time stamp (a Date object) the log message was produced first </LI>
     *   <LI> the number of times (an integer) the log message was produced </LI>
     * </OL>
     * </P>
     *
     * @see mRepeatedLogMessageMemory
     */
    private static class RepeatedLogMessage
    {
        public String logMessage;
        public Date firstTime;
        public int nrOfTimes;
    }

    // ************************************************************
    // Constants
    // ************************************************************

    /**
     * The maximum size of the log file, currently 5 MB
     */
    protected static final long MAX_FILE_SIZE = 5*1024*1024;

    /**
     * The maximum number of backup log files, currently 10
     */
    protected static final int MAX_BACKUP_LOG_FILES = 10;

    /**
     * Turns the log off, no messages are logged
     */
    public static final int DEACTIVATE_LEVEL = 0;

    /**
     * This level should be used for fatal errors, normal errors and warnings.
     */
    public static final int ERROR_LEVEL = 1;

    /**
     * This level should be used for SQL logging.
     */
    public static final int INFO_LEVEL = 2;

    /**
     * This level should be used for more detailed messages.
     */
    public static final int DETAIL_LEVEL = 3;

    /**
     * This level should be used only for debugging. Never in the production
     * release.
     */
    public static final int DEBUG_LEVEL = 4;

    /**
     * This String separates the items within one line of logging.
     */
    protected static final String cSeparator = " | ";

    /**
     * The amount of milliseconds within which the same message should not be sent twice per mail.
     */
    protected static final long cMillisToSuppressMail = 30000;

    // ************************************************************
    // Variables
    // ************************************************************

    protected PrintStream mPrintStream;
    protected int         mLoggingLevel;
    protected long        mFileSize;
    protected String      mLogFileName;
    protected boolean     mLoggingEnabled;
    protected String      mOriginator;
    protected String      mMailHost;
    protected String      mAdminMail;

    /**
     * A Hashtable to store the different KFMLogs in use, identified by their originators.
     */
    static protected Hashtable mLogs = new Hashtable();

    /**
     * Second log file.
     * This can be used if you want to have one log file for servlet and DB
     * logs and a second one only for the DB logs
     */
    protected KFMLog mSecondLog=null;

    /**
     * A Hashtable to memorize what log messages have been produced repeatedly.
     * Needed, to ensure that the same log message is not sent twice
     * within the time span of cMillisToSuppressMail.
     *
     * The keys are the log message (the subject of the mail),
     * the values are objects of the inner class RepeatedLogMessage.
     */
    protected Hashtable mRepeatedLogMessageMemory = new Hashtable();

    /**
     * The name of the server on which our software is running
     */
    protected static final String mHostname=getHostName();

    // ************************************************************
    // static Method
    // ************************************************************

    /** Return the key for the hashtable belonging to aOriginator.
     */
    private static String getOriginatorKey(String aOriginator)
    {
        return aOriginator + "(" + mHostname + ")";
    }

    /** Return the log file belonging to the servlet `aOriginator�.
     *
     * <P>Every Servlet has its own log file. But every servlet instance
     * (of the same Servlet) should use the same log file. Therefore we
     * use the singleton pattern with a hash that maps servlets to log files.
     * Different servlets however (f.e. SieCD2 and SieMap) should use
     * different log files. Therefore we use an Originator to identify
     * servlet instances belonging to the same servlet.</P>
     */
    public static synchronized KFMLog getInstance(
        String aOriginator,
        String aLogFileName,
        int aLogLevel,
        String aAdminMail,
        String aMailHost)
    {
        KFMLog tLog = (KFMLog) mLogs.get(getOriginatorKey(aOriginator));
        if(null == tLog) {
            tLog = new KFMLog(aOriginator, aLogFileName, aLogLevel);
            mLogs.put(getOriginatorKey(aOriginator), tLog);
        }
        tLog.setAdminMail(aAdminMail);
        tLog.setMailHost(aMailHost);
        return tLog;
    }

    /** Return the log file belonging to the servlet `aOriginator� and set a secondary log.
     *
     * The secondary Log will be set to aSecondLog. For an explanation of secondary logs,
     * read the class docu above.
     */
    public static synchronized KFMLog getInstance(
        String aOriginator,
        String aLogFileName,
        int aLogLevel,
        KFMLog aSecondLog,
        String aAdminMail,
        String aMailHost)
    {
        KFMLog tLog = getInstance(aOriginator, aLogFileName, aLogLevel, aAdminMail, aMailHost);
        tLog.setSecondaryLog(aSecondLog);
        return tLog;
    }

    /** Return the log file belonging to the servlet `aOriginator�.
     *
     * <P>This method may be called from within a class that needs access to
     * (the one) log file of the originator servlet, but that does not know
     * about the 'other' log attributes (like the log file name).</P>
     *
     * <P>If you are sure, that some other object has already created the log file before
     * (by use of the other getInstance() methods), use this method.
     * If this assumption does not hold, however, this method will return null,
     * so you better check for this.</P>
     *
     * @param aOriginator a String identifying the log instance
     * @return the KFMLog object for this originator (or null)
     */
    public static synchronized KFMLog getInstance(String aOriginator)
    {
        return (KFMLog) mLogs.get(getOriginatorKey(aOriginator));
    }

    /**
     * This method returns the hashtable containing all registered KFMLogs. It will
     * be invoked by the admin servlet to display the current log levels to the administrator.
     * The administrator has the possiblity to modify the log level at runtime.
     */
    public static synchronized Hashtable getRegisteredLogs()
    {
        return mLogs;
    }

    /**
     * This method returns the name of the host on which the software is running.
     */
    protected static String getHostName() {
        String tHostname="Unkown";
        try {
             tHostname= java.net.InetAddress.getLocalHost().getHostName();
        } catch (Exception e){}
        return tHostname;
    }

    // ************************************************************
    // Methods
    // ************************************************************

    /** Constructor for KFMLog.
     *
     * It opens a PrintWriter for the given logFile aLogFile. The
     * logging level will be set to aLogLevel.
     *
     * @param aOriginator   Originator of the log file
     * @param aLogFileName  LogFile with complete path
     * @param aLogLevel     Logging Level
     */
    private KFMLog (
        String aOriginator,
        String aLogFileName,
        int    aLogLevel)
    {
        mLoggingEnabled = false;
        if (aOriginator == null){
            throw new ProgrammerException("Wrong Originator!");
        } else if (aLogFileName == null){
            throw new ProgrammerException("Wrong LogFileName!");
        } else if ((aLogLevel > DEBUG_LEVEL) || (aLogLevel < DEACTIVATE_LEVEL)){
            throw new ProgrammerException("Wrong LogLevel!");
        } else {
            try {
                // open the file aLogFile in append and autoflush mode
                mFileSize = (new File(aLogFileName)).length();
                FileOutputStream tFileOutputStream = new FileOutputStream(aLogFileName, true);
                mPrintStream   = new PrintStream(new BufferedOutputStream(tFileOutputStream, 4096) , true);
                mLoggingLevel  = aLogLevel;
                mLogFileName   = aLogFileName;
                mLoggingEnabled = true;
                mOriginator    = aOriginator+"("+mHostname+")";
            } catch (IOException e) {
                mLoggingEnabled = false;
                throw new ProgrammerException("Couldn't write to log file!\n" +
                    "Path:" + aLogFileName + "\n" +
                    Converter.exceptionStackToString(e));
            }
        }
    }

    /** */
    protected KFMLog ()
    {
    }

    /**
     * Logs a text string at the logging level specified.
     *
     * This method is used when your code needs to parameterize
     * the logging level.  Otherwise you probably should use the
     * error(), warning(), info() methods below, they will be less verbose in your
     * code.
     *
     * Note: We moved the synchronize block into the method to avoid performance problems.
     */
    public void logString(
        String aOriginator,
        String aLogString,
        int aErrorLevel)
    {
        String tErrorLevel = getErrorLevelString(aErrorLevel);

        // Events with same or lower Level as mLoggingLevel will be logged
        if (mLoggingEnabled && (aErrorLevel <= mLoggingLevel)){
            String tMessage =
                        KFM_DateTimeService.createTimeStamp() + cSeparator +
                        tErrorLevel + cSeparator +
                        aOriginator + cSeparator +
                        Thread.currentThread().getName()+ cSeparator +
                        aLogString;

            synchronized(this) {
                // if current logFile reaches max size, create new one and save old
                // files
                if (mFileSize >= MAX_FILE_SIZE){
                    createNewLogFileAndSaveOldFiles();
                }
                // write logString in logFile if errorLevel is equal or less mLoggingLevel
                mFileSize += tMessage.length();
                mPrintStream.println(tMessage);

                // We use a BufferedOutputStream, therefore flushing is done automatically
                //mPrintStream.flush();
            }
        }
        if (mSecondLog != null){
            mSecondLog.logString(aOriginator, aLogString, aErrorLevel);
        }
    }

    /**
     * Returns the String belonging to the given errorLevel
     */
    public static String getErrorLevelString (int aErrorLevel)
    {
        String tErrorLevel="DEACTIVATE_LEVEL";
        switch (aErrorLevel){
            case ERROR_LEVEL:  tErrorLevel="ERROR_LEVEL";
                               break;
            case INFO_LEVEL:   tErrorLevel="INFO_LEVEL";
                               break;
            case DETAIL_LEVEL: tErrorLevel="DETAIL_LEVEL";
                               break;
            case DEBUG_LEVEL:  tErrorLevel="DEBUG_LEVEL";
        }
        return tErrorLevel;
    }

    /**
     * Returns the errorLevel as int
     */
    public static int getErrorLevelInt (String aErrorLevel)
    {
        if (aErrorLevel.equals("ERROR_LEVEL")){
            return ERROR_LEVEL;
        } else if (aErrorLevel.equals("INFO_LEVEL")){
            return INFO_LEVEL;
        } else if (aErrorLevel.equals("DETAIL_LEVEL")){
            return DETAIL_LEVEL;
        } else if (aErrorLevel.equals("DEBUG_LEVEL")){
            return DEBUG_LEVEL;
        }
        return DEACTIVATE_LEVEL;
    }

    /**
     * Logs a text string at ERROR_LEVEL and sends an email.
     * The email subject and email content both contain aLogString.
     *
     * @deprecated use #error(String, String) instead
     */
    public void error(String aLogString)
    {
        logString(mOriginator, aLogString, ERROR_LEVEL);
        sendMail("ERROR "+ mOriginator + ": " +aLogString, "");
    }

    /**
     * Logs the concatenation of aLogString and an exception stack trace
     * at ERROR_LEVEL and sends an email.
     * The email subject consists of aLogString and the
     * email content consists of the concatenation of aLogString
     * and the exception stack trace.
     *
     * @deprecated use #error(String, String, Throwable) instead
     */
    public void error(String aLogString, Throwable aException)
    {
        if (aException == null) {
            error(aLogString);
            return;
        }

        // Get a stack trace into a string.
        String tStackTrace = Converter.exceptionStackToString(aException);

        logString(mOriginator, aLogString+"\n"+tStackTrace, ERROR_LEVEL);
        sendMail("ERROR "+ mOriginator + ": " +aLogString, tStackTrace);
    }

    /**
     * Logs the concatenation of the subject and content string at ERROR_LEVEL
     * and sends an email.
     * The email subject consists of aLogStringSubject and the
     * email content consists of aLogStringMessage.
     *
     * 2003-05-02: The email subjects are often very long and thus very hard to read in Outlook.
     * So I find it helpful to repeat `aLogStringSubject� in the mail body. If you disagree, then let's
     * vote in the team meeting.
     */
    public void error(String aLogStringSubject, String aLogStringMessage)
    {
        logString(mOriginator, aLogStringSubject + ":" + aLogStringMessage, ERROR_LEVEL);
        sendMail("ERROR "+ mOriginator + ": " +aLogStringSubject,
                 aLogStringSubject + "\n" + aLogStringMessage, "");
    }

    /**
     * Logs the concatenation of the subject string, the content string
     * and an exception stack trace at ERROR_LEVEL and sends an email.
     * The email subject consists of aLogStringSubject and the
     * email content consists of the concatenation of aLogStringMessage
     * and the exception stack trace.
     *
     * 2003-05-02: The email subjects are often very long and thus very hard to read in Outlook.
     * So I find it helpful to repeat `aLogStringSubject� in the mail body. If you disagree, then let's
     * vote in the team meeting.
     */
    public void error(String aLogStringSubject, String aLogStringMessage, Throwable aException)
    {
        if (aException == null) {
            error(aLogStringSubject, aLogStringMessage);
            return;
        }

        // Get a stack trace into a string.
        String tStackTrace = Converter.exceptionStackToString(aException);

        logString(mOriginator, aLogStringSubject + ":" + aLogStringMessage+"\n"+tStackTrace, ERROR_LEVEL);
        sendMail("ERROR "+ mOriginator + ": " +aLogStringSubject,
                 aLogStringSubject + "\n" + aLogStringMessage, tStackTrace);
    }

    /**
     * Logs a text string at INFO_LEVEL
     */
    public void info(String aLogString)
    {
        logString(mOriginator, aLogString, INFO_LEVEL);
    }

    /**
     * Logs a text string and an exception at INFO_LEVEL
     */
    public void info(String aLogString, Exception aException)
    {
        if (aException == null) {
            info(aLogString);
            return;
        }

        // Get a stack trace into a string.
        String tStackTrace = Converter.exceptionStackToString(aException);

        logString(mOriginator, aLogString+"\n"+tStackTrace, INFO_LEVEL);
    }

    /**
     * Logs a text string at DETAIL_LEVEL
     */
    public void detail(String aLogString)
    {
        logString(mOriginator, aLogString, DETAIL_LEVEL);
    }

    /**
     * Logs a text string at DEBUG_LEVEL
     */
    public void debug(String aLogString)
    {
        logString(mOriginator, aLogString, DEBUG_LEVEL);
    }

    /**
     * Tests whether the log is deactivated, i.e. whether we debug everything
     * at level DEACTIVATE_LEVEL or below.
     */
    public boolean isDeactivateLevel()
    {
        return (mLoggingLevel >= DEACTIVATE_LEVEL);
    }

    /**
     * Tests whether the log is in Error level, i.e. whether we debug everything
     * at level ERROR_LEVEL or below.
     */
    public boolean isErrorLevel()
    {
        return (mLoggingLevel >= ERROR_LEVEL);
    }

    /**
     * Tests whether the log is in Info level, i.e. whether we debug everything
     * at level INFO_LEVEL or below.
     */
    public boolean isInfoLevel()
    {
        return (mLoggingLevel >= INFO_LEVEL);
    }

    /**
     * Tests whether the log is in Detail level, i.e. whether we debug everything
     * at level DETAIL_LEVEL or below.
     */
    public boolean isDetailLevel()
    {
        return (mLoggingLevel >= DETAIL_LEVEL);
    }

    /**
     * Tests whether the log is in Debug level, i.e. whether we debug everything
     * at level DEBUG_LEVEL or below.
     */
    public boolean isDebugLevel()
    {
        return (mLoggingLevel >= DEBUG_LEVEL);
    }

    /**
     * Gets the current logging level
     */
    public int getLogLevel()
    {
        return mLoggingLevel;
    }

    /**
     * Sets the current logging level
     */
    public void setLogLevel(int aLogLevel)
    {
        mLoggingLevel = aLogLevel;
    }

    /**
     * Gets the originator
     */
    public String getOriginator()
    {
        return mOriginator;
    }

    /**
     * Gets the log file name
     */
    public String getLogFileName()
    {
        return mLogFileName;
    }

    /**
     * Sets the secondary Log
     */
    public void setSecondaryLog(KFMLog aSecondLog)
    {
        mSecondLog=aSecondLog;
    }

    /**
     * Sets the mail host
     */
    public void setMailHost(String aMailHost)
    {
        mMailHost=aMailHost;
    }

    /**
     * Sets the admin email address
     */
    public void setAdminMail(String aAdminMail)
    {
        mAdminMail=aAdminMail;
    }

    /**
     * Gets the mail host
     */
    public String getMailHost()
    {
        return mMailHost;
    }

    /**
     * Gets the admin email address (may also be null)
     */
    public String getAdminMail()
    {
        return mAdminMail;
    }

    /**
     * Sends mail to receivers (only if mAdminMail and mMailHost are set).
     * Don't send mail twice within cMillisToSuppressMail.
     * It uses a given subject and a given message inside the mail.
     */
    protected void sendMail (
            String aSubject,
            String aMessage,
            String aStackTrace)
    {
        // nothing to do if mAdminMail or mMailHost are not set
        if ((mAdminMail == null) || (mMailHost == null)) {
            return;
        }

        // Hava a look at mRepeatedLogMessageMemory
        // Did we sent class within the last cMillisToSuppressMail ?
        RepeatedLogMessage tRepLog = (RepeatedLogMessage) mRepeatedLogMessageMemory.get(aMessage);
        boolean tSend = true;
        String tRepetition = "";

        if (tRepLog == null) {
            // Log message never happened before: we send the mail.
            // Additionally produce a RepeatedLogMessage item and store it within mLogMessageMemory.
            tRepLog = new RepeatedLogMessage();
            tRepLog.logMessage = aMessage;
            tRepLog.firstTime = new Date();
            tRepLog.nrOfTimes = 1;
            mRepeatedLogMessageMemory.put(aMessage, tRepLog);
        } else {
            // Log message happened before: test if this entry is older than cMillisToSuppressMail.
            long tMillisDifference = (new Date()).getTime() - tRepLog.firstTime.getTime();
            if (tMillisDifference > cMillisToSuppressMail) {
                // It is older: we send the mail.
                // Additionally, we prepare a repetition info string and
                // reset the values of the RepeatedLogMessage item.
                tRepLog.firstTime = new Date();
                if (tRepLog.nrOfTimes > 1) {
                    tRepetition = "... This message happened " + (tRepLog.nrOfTimes - 1) +
                    " times within the last " (cMillisToSuppressMail/1000) + " seconds ...\n\n";
                }
                tRepLog.nrOfTimes = 1;
            } else {
                // it is not older: we don't send the mail.
                // Instead, we increase nrOfTimes
                tSend = false;
                tRepLog.nrOfTimes += 1;
            }
        }

        if (tSend) {
            // Construct the SMTP message
            String tSmtpMessage = "Subject: " + aSubject + "\n\n"
                + tRepetition + KFM_DateTimeService.createTimeStamp() + ": " + aMessage+ "\n"
                + Thread.currentThread().getName()+"\n"+aStackTrace;

            // the "from" header may only contain one e-mail address, otherwise mail is ignored,
            // therefore when mAdminMail contains several addresses, we use only the first as sender
            String tFrom = mAdminMail;
            int tIndex = tFrom.indexOf(',');
            if (tIndex != -1) {
                tFrom = tFrom.substring(0, tIndex);
            }

            // *Send* mail via Smtp class. Yes, really, the constructor *sends* a mail.
            Smtp tMail = new Smtp(mMailHost, mAdminMail, tFrom, tSmtpMessage, true);
        }
    }

    /**
     * Sends mail to receivers (only if mAdminMail and mMailHost are set).
     * Use the message as subject of the mail.
     * Don't send mail twice within cMillisToSuppressMail.
     *
     * @deprecated
     */
    protected void sendMail (
        String aMessage,
        String aStackTrace)
    {
        sendMail(aMessage, aMessage, aStackTrace);
    }

    /**
     * Creates a new log file with the name mLogFileName and stores the old log files.
     * The old log files will be renamed to mLogFileName1, mLogFileName2, ..., mLogFileName10.
     * The oldest log file will be overwritten.
     */
    private void createNewLogFileAndSaveOldFiles()
    {
        // close current log file
        mPrintStream.flush();
        mPrintStream.close();

        try {
            // rename existing log files
            for(int i = MAX_BACKUP_LOG_FILES - 1; i >= 0; i--) {
                // special case handling: there is no 'log0' it's just 'log'
                FileUtils.moveFile(
                    new File(mLogFileName + (0 == i ? "" : "" + i)),
                    new File(mLogFileName + (i+1)));
            }

            // open new log file (with the same name)
            FileOutputStream tFileOutputStream =
                new FileOutputStream(mLogFileName);
            mPrintStream = new PrintStream(
                new BufferedOutputStream(tFileOutputStream, 4096), true /* autoflush */);
            mFileSize = 0;

            mLoggingEnabled = true;
        } catch (IOException ioe){
            sendMail("CRITICAL ERROR: Logging will be disabled",
                Converter.exceptionStackToString(ioe));

            mLoggingEnabled = false;
        }
    }
}
TOP

Related Classes of KFM.log.KFMLog

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.