Logger framework

Introduction

The aim of this package was not to implement a new logging mechanism. The idea of this framework was more to have a simple logging framework which enables to replace the main logging mechanism after developing a software.
This happens as example when you develope a third party library. In this case you can use the jptools logging mechanism. After developing the library it will used (integrate) in a project. Probably the project has different requirements to the logger mechnism than you defined in your library.
Currently a lot of libraries are using only a simple OutputStream to write the log. The OutputStream can be set. This is a simple solution but the user of such a library has no change to filter some output of the log output or can change the output format.
The jptools logger is a fast and simple logging mechanism. The logger has no special overhead or a big footprint. The implementation is straight foreward but contains the main functionality of a logger:

  • The logger supports the following logging levels (which are defined in te Level class):

    • INFO

    • DEBUG

    • WARN

    • ERROR

    • FATAL

    • PROFILE

    User defined log levels are also supported!
  • The logger can be configured with an instance of the class LogConfig. The getLogger and setLogger are methods of the class Logger to enables the access to the configuration.
  • The format of the logger output is defined by the used layout class (Layout).
  • The writer class (LogWriter) implements the concrete logger.
  • Some bindings to other logger frameworks are also supported:

To sum up, the jptools logging mechanism enables you to have the control of the logger not only in your project but also in all third party libraries which are using this logging mechanism.


Design

The Logger class

The main class of this package is the Logger. Each class which have to log defines an instance of this class:

public class Demo
{
    private static final Logger log = Logger.getLogger( Demo.class );
    ...
}

The Logger class defines methods to log to a special level like info(...) which logs a simple information. The logger holds also a member attribute in the background, an instance of a LogWriter. Each class which implements this interface implements a concrete logger.

The following classdiagram gives an overview of the logging framework:



The LogWriter classes

A LogWriter which is wrapped by the Logger class implements the concrete logger. The LogWriter use an Appender to write the message (LogMessage).

The class diagramm gives an overview of the implemented LogWriter classes:


The following LogWriter implementations are available:

  • AbstractFeatureSupportLogWriter: This class implements some general features which should not implemented by each LogWriter class like the support of the version number etc.
    This class is the base class of all LogWriter classes. This class works with the Appender. Each subclass returns with the method getAppender() a concrete implementation.
  • AbstractLogWriter: This LogWriter class implements class context feature.
  • DispatchLogWriter: This class supports the feature to dispatch the LogMessage to mulitple Appender's.
  • FileLogWriter: This class use the FileAppender to implements functionality to log to a file.
  • NullLogWriter: This class implements the interface but do nothing in the background. It is used to disable the logger.
  • SimpleLogWriter: This class writes the output over the StreamAppender to a defined stream like the standard output (stdout) or standard error (stderr).
  • StreamLogWriter: This log writer extends an output stream. It can be used to log to the logger over a stream. Different java libraries offers only an OutputStream where logs will be written to. This log writer gives a simple way to write this logs also to the logger.

The key logger.writer defines in the logger configuration which LogWriter should be used. The defaut value is jptools.logger.SimpleLogWriter. (see The logger configuration).


The Appender classes

An Appender implements where the logger output will written to. The class diagramm gives an overview of the implemented Appender classes:


The following Appender implementations are available:

  • AbstractAppender: This class is the base class of all appenders. Its implement a named cache where appender information can be stored or loaded by a given key.
  • CommonAppender: This class appends the log messages to the common logger framework.
  • DatabaseAppender: This class appends the log messages to a table in a database. This implementation is a simple buffer which collects the last log entries. Only if a log entry with specific level occurs it logs the defined last log entries over a defined appender.
  • DailyFileAppender: This appender implements the functionality to change filenames automaticly. Note that the filename doesn't have to change every day, making it possible to have logfiles which are per-week or per-month.
  • FileAppender: This appender writes the output to a file. The filename can be configured in the LogConfig with the key logger.destination.
  • JDKAppender: This class appends the log messages to the JDK logger framework.
  • JMSQueueAppender: This appender logs the messages to a JMS queue.
  • JMSTopicAppender: This appender logs the messages to a JMS topic.
  • LogRotateBufferAppender: This class implements a simple buffer which collects the last log entries. Only if a log entry with specific level occurs it logs the defined last log entries over a defined appender.
  • MailAppender: This class implements a simple buffer which collects the last log entries. Only if a log entry with specific level occurs it logs the defined last log entries over mail (smtp).
  • RollingFileAppender: This appender writes the output to a file which can grow to a defined size. If it is reached the file rotates to a backup file. The number of backup files can be configured. If the max. number of backup files is reached than the oldest file will delete.
  • StreamAppender: This class writes the output to a defined stream.

The Appender class use the defined Layout to write the message to a stream, file etc.


The Layout class

The definition of the output of a simple log line will decided by the used layout. The interface Layout defines the simple API. The following layout implementation are available:

  • AbstractLayout: This class implements some useful helper methods which can be used to write your own layout class.
  • SimpleLayout: This class implements the layout to log the output as plain ASCII. This layout supports the timestamp, the log-level, some additional log information, the message, etc.
  • XMLLayout: This class implements a simple XML layout structure which can be used as logger layout.

The following classdiagram gives an overview of the logger layout:

The key logger.layout defines in the logger configuration which Layout instance should be used. The defaut value is jptools.logger.SimpleLayout. (see The logger configuration).



Filtering log messages

Filtering classes and packages

The class AbstractAppender uses the Filter class to filter the logger output. It enables to filter whole package structures or just a simple class out. Each subclass of the AbstractAppender can use the method checkFilter to test in its writeMessage method if the message should logged or not.

The mapping role is simple:

  • Each filter role defines a level for a package or class.
  • The level definition is either a simple level or neither a level expression. A level expression is a string which contains levels whith arithmetic characters like + and -.
    Examples:
    • log only ERROR messages: ERROR
    • log INFO, DEBUG and ERROR log messages: INFO + DEBUG + ERROR
    • log ALL messages except DEBUG and UNKNOWN messages: ALL - DEBUG - UNKNOWN

    IMPORTANT: The level names in this example are equal to this of the logger configration (see above). If you change the keys logger.allText, logger.offText etc. than you have also to modify the filter roles.

  • If the role contains a package or class wich is a or in a subpackage of an already defined package than the last entry wins (last means the deepest in the class hierarchy).
    Examples:
    • log everything of the com.test package:
      logger.filter.com.test = ALL

    • log only WARN, ERROR and FATAL of the com.test package:
      logger.filter.com.test = WARN + ERROR + FATAL

    • log only WARN, ERROR and FATAL of the com.test package. But log also DEBUG logs of the MainTest class :
      logger.filter.com.test = WARN + ERROR + FATAL
      logger.filter.com.test.MainTest = WARN + ERROR + FATAL + DEBUG

    • log only WARN, ERROR and FATAL of the com.test package. But log also DEBUG logs of the MainTest class. Log everything of the subpackage runs:
      logger.filter.com.test = WARN + ERROR + FATAL
      logger.filter.com.test.runs = ALL
      logger.filter.com.test.MainTest = WARN + ERROR + FATAL + DEBUG


Filtering additional information

The AbstractAppender also supports an additional user defined filter. Each log method of the class Logger like info, warn, error and fatal supports as first parameter a LogInformation instance.

The Interface LogInformation defines only the method getLogInformation to print out the additional log information. The aim of this additional log information is to filter at runtime in use with regular expression the user defined log entries.

This feature also can be used in combination with the class and package filter!

Key

Default value

Description

logger.logInformationFilter

Defines the log information filter. The filter should to be a regular expression. Example: Filter all log information which ends with myName: myName$


The logger configuration (LogConfig)

The class LogConfig implements the configuration of the logger. The methods getConfig and setConfig of the class Logger could be used to receive or change settings at runtime. The methods getProperty and setProperty can be used to get or set a logger attribute.

LogConfig conf = Logger.getConfig();
conf.setProperty( LogConfig.LOG_WRITER, "jptools.logger.SimpleLogWriter" );
conf.setProperty( LogConfig.LOG_LAYOUT, "jptools.logger.SimpleLayout" );
Logger.setConfig( conf );

In the sub chapters all settings of the logger configuration in the jptools.properties are explained. The link shows a whole example of a logger configuration.


General logger configuration

The following table shows the general logger configuration:

Key

Default value

Description

logger.writer

jptools.logger.SimpleLogWriter

Defines the LogWriter to use

logger.level

ALL

Defines the enabled level to use. See also chapter filtering

logger.layout

jptools.logger.SimpleLayout

Defines the Layout to use

logger.writerContext

default

Defines to default writer context name

logger.supportMBean

true

Enable/disabe the mbean support.

logger.rmiLogging

false

Defines to do RMI logging

logger.enableBootstrapLog

false

Enable/disabe the bootstrap log information. Mostly used to debug.

logger.enableStatistic

false

Enable/disabe the statistic information. The logger statistic log only the WARN, ERROR and FATAL calls. The method getLogStatistic() returns a map with the logger statistic. The key is the level string for example Level.WARN.toString() and the value is an Integer value.
The method logStatistic() logs the statistic to the logger.

logger.enableLogInformation

false

Enable/disabe the log information in the header

logger.enableVersion

false

Enable/disabe the version information in the header. The version number of a class is taken from the attribut VERSION if it exist. The RCS/CVS notation is allowd. It follows an example:

    /** Version-number for version-control */
    public static final String VERSION = "$Revision: 1.0 $";
         

logger.enableHierarchy

false

Enable/disabe the hierarchy log

logger.enableStacktraceInfo

false

Enable/disabe the additional stacktrace information.

Performance: DON'T USE FOR PRODUCTION USE!


Configuration of the StreamAppender (also SimpleLogWriter)

The following table shows StreamAppender specific settings:

Key

Default value

Description

logger.destination

System.out

Defines the outputstream to use. The following values are valid:

  • System.out
  • System.err


Configuration of the FileAppender (also FileLogWriter)

The following table shows FileAppender specific settings:

Key

Default value

Description

logger.destination

jptools.log

Defines the name of the log output.

logger.appendFile

false

Append the log file: true / false


Configuration of the DailyFileAppender

The following table shows DailyFileAppender specific settings:

Key

Default value

Description

logger.destination

jptools-'yyyy-MM-dd'.log

Defines the name of the log output. As example:
/log/filename-'yyyy-MM-dd'.log ==> /log/filename-2002-12-31.log
/log/filename-'yyyy-ww'.log ==> /log/filename-2002-52.log

logger.appendFile

false

Append the log file: true / false

logger.currentDestination

Defines the current name if it should be different. If it is not defined As example:
/log/filename.log


Configuration of the JMSQueueAppender

The following table shows JMSQueueAppender specific settings:

Key

Default value

Description

logger.java.naming.factory.initial

weblogic.jndi.WLInitialContextFactory

Defines the context factory.

logger.java.naming.provider.url

t3://127.0.0.1:80

Defines the JMS url.

logger.jmsConnectionFactory

LoggerConnectionFactory

Defines the JMS connection factory.

logger.queueName

LoggerQueue

Defines the JMS queue name.


Configuration of the JMSTopicAppender

The following table shows JMSTopicAppender specific settings:

Key

Default value

Description

logger.java.naming.factory.initial

weblogic.jndi.WLInitialContextFactory

Defines the context factory.

logger.java.naming.provider.url

t3://127.0.0.1:80

Defines the JMS url.

logger.jmsConnectionFactory

LoggerConnectionFactory

Defines the JMS connection factory.

logger.topicName

LoggerTopic

Defines the JMS topic name.


Configuration of the RollingFileAppender

The following table shows RollingFileAppender specific settings:

Key

Default value

Description

logger.destination

jptools.log

Defines the name of the log output.

logger.fileSize

10M

Defines the max. file size. The number can be followed by K, M, G and T (kilo, mega, giga and tera bytes).

logger.maxBackup

1

Defines the max. number of backup files.


Configuration of the LogRotateBufferAppender

The following table shows LogRotateBufferAppender specific settings:

Key

Default value

Description

logger.size

10

Defines the size of the buffer.

logger.alert

ERROR + FATAL

Defines the alert level to log out the buffer.

logger.subAppender

StreamAppender

Defines the appender which is used to real log the buffered entries.

logger.logrotateStart

. . .

Defines the start string of each log entry. If it is empty it doesn't appear.

logger.logrotateEnd

Defines the end string of each log entry.. If it is empty it doesn't appear.


Configuration of the MailAppender

The MailAppender is a subclass of the LogRotateBufferAppender. The configuration of the MailAppender is the same like the LogRotateBufferAppender. The additional configuration is used to the define mail specific settings:

Key

Default value

Description

logger.hostname

localhost

Defines the mail hostname.

logger.mailFrom

Defines the from address of the mail.

logger.mailTo

Defines the to address of the mail.

logger.mailSubject

jptools - logger

Defines the mail subject.


Configuration of the DatabaseAppender

The DatabaseAppender is a subclass of the LogRotateBufferAppender. The configuration of the DatabaseAppender is the same like the LogRotateBufferAppender. The additional configuration is used to the define mail specific settings:

Key

Default value

Description

logger.tableName

jptoolsLogTable

Defines the default table.

logger.datasourceName

Defines the DataSource. To use the DatabasePoolManager as DataSource define no datasource name but add the DatabasePoolManager configuration. As example:
logger.database.username = myUser
logger.database.errorTimeout = 100000

logger.createTable

true

Create table if it does not exist.


Configuration of the DispatchLogWriter

The DispatchLogWriter configuration starts with logger.dispatch. and follows by a user defined name. After the name followed by a dot (.) the normal LogConfig configuration follows.

The following example use as default the SimpleLayout and uses the DispatchLogWriter. It contains three different appenders (main, file, dailyFile):

logger.layout                    = jptools.logger.SimpleLayout
logger.writer                    = jptools.logger.DispatchLogWriter

# main: log all debug logs to the console 
logger.dispatch.main.appender    = jptools.logger.appender.StreamAppender
logger.dispatch.main.filter.jptools = DEBUG

# file: log to a file with name appender-file.txt. Log only warning, errors and fatals. 
#       The output starts with the month and day etc.
logger.dispatch.file.appender    = jptools.logger.appender.FileAppender
logger.dispatch.file.destination = appender-file.txt
logger.dispatch.file.filter.jptools = WARN + ERROR + FATAL
logger.dispatch.file.dateFormat  = MM.dd-HH\:mm\:ss.SSS

# dailyFile: log to a file which contains the year, month and day in the filename. The filename
#            will created every day new
logger.dispatch.dailyFile.appender    = jptools.logger.appender.DailyFileAppender
logger.dispatch.dailyFile.destination = trace-'yyyy-MM-dd'.log


Configuration of the AbstractLayout and SimpleLayout

The following table shows AbstractLayout and SimpleLaoyut specific settings:

Key

Default value

Description

logger.allText

ALL

Defines the ALL Level text

logger.offText

OFF

Defines the OFF Level text

logger.infoText

INFO

Defines the INFO Level text

logger.warningText

WARN

Defines the WARN Level text

logger.debugText

DEBUG

Defines the DEBUG Level text

logger.errorText

ERROR

Defines the ERROR Level text

logger.fatalText

FATAL

Defines the FATAL Level text

logger.unknownText

UNKNOW

Defines the UNKNOW Level text

logger.exceptionText

EXCEPTION\:

Defines the exception start message

logger.dateFormat

yyyy.MM.dd-HH\:mm\:ss.SSS

Defines the date format

logger.itemSeparator

\ -\

Defines the item separator (separator between the header information)

logger.messageSeparator

\ |\

Defines the message separator (separator between the header and the message)

logger.fillupCharacter

\

Defines the character to fillup header messages if they have a too short length

logger.hierarchyStartTag

<

Defines the hierarchy start tag in the message header

logger.hierarchyEndTag

>

Defines the hierarchy end tag in the message header

logger.hierarchyIndent

\ \

Defines the string to indent hierarchy log messages. If the string is empty no indention will done.

logger.hierarchyIndentStart

Defines the start string for indention of the hierarchy log messages. If the string is empty no indention will done.

logger.hierarchyMaxLevel

-1

Defines the max. hierarchy level to indent. A negative value defines no limitation.

logger.enableThreadName

false

Enable/disabe the thread name in the header message

logger.enableTimeStamp

true

Enable/disabe the time stamp in the header message

logger.enablePackageName

true

Enable/disabe the package name in the header message

logger.enableClassName

true

Enable/disabe the class name in the header message

logger.enableLevel

true

Enable/disabe the level in the header message

logger.enableMessage

true

Enable/disabe the log message

logger.enableExceptionStackTrace

true

Enable/disabe to show the stacktrace information by an exception

logger.enableHierarchyInHeader

false

Enable/disabe the hierarchy level in the log header

logger.enableHierarchyCorrection

false

Enable/disabe the hierarchy correction. Often the output of a filtered hierarchy log do not looks very well. The correction tries to solve the output presentation problem.

logger.levelFieldWidth

1

Defines the with of the level in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length
  • logger.threadFieldWidth

    10

    Defines the with of the thread name in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length
  • logger.callTraceFieldWidth

    40

    Defines the with of the thrace information in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length
  • logger.logInformationFieldWidth

    10

    Defines the with of the log information (LogInformation) in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length
  • logger.versionFieldWidth

    4

    Defines the with of the version in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length
  • logger.hierarchyFieldWidth

    4

    Defines the with of the hierarchy level in the header field. There are two possibilities:

  • greater than zero to define the max. length of this column
  • less than zero to define individual length

  • Examples

    Simple logging

    import jptools.logger.Logger;
    
    
    public class Demo
    {
        private static final Logger log = Logger.getLogger( Demo.class );
    
        public static void main( String[] args ) throws Exception
        {
            log.info( "This is a simple info message" );
            log.debug( "This is a simple debug message" );
            log.warn( "This is a simple warn message" );
            log.error( "This is a simple error message" );
            log.fatal( "This is a simple fatal message" );
        }
    }
    

    Logging with excpetion

    import jptools.logger.Logger;
    
    
    public class Demo
    {
        private static final Logger log = Logger.getLogger( Demo.class );
    
        public static void main( String[] args ) throws Exception
        {
            try
            {
               ...
            }
            catch( Throwable t )
            {
               log.error( "An exception occured!", t );
            }
        }
    }
    

    Logging with level check

    import jptools.logger.Logger;
    
    
    public class Demo
    {
        private static final Logger log = Logger.getLogger( Demo.class );
    
        public static void main( String[] args ) throws Exception
        {
            // this is used if the log output takes time and is expensive!
            if( log.isDebugEnabled() )
            {
               for( int i=0; i<100; i++ )
                   log.debug( "The debug message " + i );
            }
        }
    }