Senior Project Design
Logging, Debugging and Bug Tracking

Course Map


Agenda


Logging Definition

Logging is the process of reporting information about a running program.


Traditional Debugging

Add println() statements to your code to keep track of the code flow. These println() statements are taken out if code starts to work properly
If changes are made, and we run into more bugs, these statements need to put back in.

Better approach would be to create a flag and method in your class

static final boolean activateDebug;
debug(String info){
if (activateDebug) System.out.println(String);
}

Calling debug() anywhere will print debug statements. If activateDebug is false, the compiler will completely remove the code within the braces (thus the code doesn�t cause any run-time overhead at all when it isn�t used). However, recompilation is required every time value of activateDebug is switched. Another solution, the value of activateDebug can be set in an external configuration file. Not generic, everyone does it differently.


Logging API

Logging API is the Java's solution for Logging in a simple way.

  • Use Configuration files to modify logging properties - No recompilation of the code required
  • Loggers - a Logger is an object that presents a number of logging methods, which ca be used to pass in messages for logging.
  • LogRecords - a LogRecord is an object representing a logging message inside the logging system. LogRecords are created by Loggers. They carry all the information about logging event where it originated, what time it occurred, what level of importance or granularity it carries, and so on.
  • Handlers - A Handler is an object that knows how to write a log file to a particular output destination - a file, a database, a memory buffer, and so on.
  • Formatters - A Formatter is an object that helps a Handler write out a LogRecord. Formatters can perform message localization, selection of which parts of message to log, and formatting of the selected data into string form.
  • Filters - A Filter is an object that helps a Logger to decide whether to pass a message on to a handler, or to ignore it, or helps a Handler decide whether to format and output a message, or ignore it.
  •  


    Logging Control Flow

    By default all Loggers also send their output to their parent Logger. But Loggers may also be configured to ignore Handlers higher up the tree.
    Handlers can also direct output to other Handlers. For example, the MemoryHandler maintains an internal ring buffer of LogRecords and on trigger events it publishes its LogRecords through a target Handler. In such cases, any formatting is done by the last Handler in the chain.

     

    Logging Example:

    import java.util.logging.*;
    
    public class InfoLogging {
    
    	private static Logger logger = Logger.getLogger("InfoLogging");
    
    	public static void main(String[] args) {
    	    
    		logger.info("Logging an INFO-level message");
    
    		logger.log(Level.INFO, "Logging an INFO-level message");
    
    		logger.logp(Level.INFO, "InfoLogging", "main",
    					"Logging an INFO-level message");
    	}
    }
    
    Output:
    Apr 15, 2006 2:25:52 PM InfoLogging main
    INFO: Logging an INFO-level message
    Apr 15, 2006 2:25:52 PM InfoLogging main
    INFO: Logging an INFO-level message
    Apr 15, 2006 2:25:52 PM InfoLogging main
    INFO: Logging an INFO-level message 

    Logging Categories

    The logging methods are grouped in five main categories:


    Log Levels

    Each log message has an associated log Level. The Level indicates importance and urgency of a log message. Log level objects encapsulate an integer value, with higher values indicating higher priorities.

    Logging API provides the ability to change to a different logging level during program execution. Thus, you can dynamically set the logging level to any of the Logging Levels.

    Levels Effect Numeric Values
    OFF No Logging Messages Reported Integer.MAX_VALUE
    SEVERE Only logging messages with the level SEVERE are reported 1000
    WARNING Logging messages with levels of WARNING and SEVERE are reported 900
    INFO Logging messages with levels of INFO and above are reported 800
    CONFIG Logging messages with levels of CONFIG and above are reported 700
    FINE Logging messages with levels of FINE and above are reported 500
    FINER Logging messages with levels of FINER and above are reported 400
    FINEST Logging messages with levels of FINEST and above are reported 300
    ALL All logging messages are reported Integer.MIN_VALUE

    For defining a custom level, you can inherit from java.util.Logging.Level. and define your own level.

    LoggingLevels.java

    LoggingLevels.txt


    Loggers

    Client code sends log requests to Logger objects. Each logger keeps track of a log level that it is interested in, and discards log requests that are below this level. Loggers are normally named entities, using dot-separated names such as java.awt. The namespace is hierarchical and is managed by the LogManager. 

    There can be multiple logger objects in your program, and these loggers are organized into a hierarchical tree, which can be programmatically associated with the package namespace. Child loggers keep track of their immediate parent and by default passing the logging records up to the parent. The root Logger (named "") has no parent. Loggers may inherit various attributes from their parents in the logger namespace. In particular, a logger may inherit:


    Logging Methods

    The Logger class provides a large set of convenience methods for generating log messages. For convenience, there are methods for each logging level, named after the logging level name. Thus rather than calling logger.log,a developer can simply call the convenience method logger.warning.

    There are two different styles of logging methods, to meet the needs of different communities of users.

              void warning(String sourceClass, String sourceMethod, String msg);

        void warning(String msg);

    For second set of methods, the Logging framework will make a "best effort" to determine which class and method called into the logging framework and will add this information into the LogRecord. However, it is important to realize that this automatically inferred information may only be approximate. The latest generation of virtual machines perform extensive optimizations and may entirely remove stack frames, making it impossible to reliably locate the calling class and method.


    Log Records

    A LogRecord is an example of a messenger object, whose job is simply to carry information from one place to another. All the methods in the LogRecord are getters and setters

    PrintableLogRecord.java

    Level<FINEST>
    LoggerName<null>
    Message<Simple Log Record>
    CurrentMillis<1145153433003>
    Params<null>
    ResourceBundle<null>
    ResourceBundleName<null>
    SequenceNumber<0>
    SourceClassName<null>
    SourceMethodName<null>
    Thread Id<10>
    Thrown<null>
    

    Handlers

    The Java logging framework provides the following Handlers:


    Writing Log Records to a Log File

    To make a logger write log records to a file, you need to add a file handler to the logger.
        try {
            // Create a file handler that write log record to a file called my.log
            FileHandler handler = new FileHandler("my.log");
        
            // Add to the desired logger
            Logger logger = Logger.getLogger("package.class");
            logger.addHandler(handler);
        } catch (IOException e) {
        }
    
    By default, a file handler overwrites the contents of the log file each time it is created. This example creates a file handler that appends.
        try {
            // Create an appending file handler
            boolean append = true;
            FileHandler handler = new FileHandler("my.log", append);
        
            // Add to the desired logger
            Logger logger = Logger.getLogger("package.class");
            logger.addHandler(handler);
        } catch (IOException e) {
        }
    

    Logging in the XML Format

    LogToFile.java

    The default output format for a FileHandler is XML. If you want to change the format, you must attach a different Formatter object to the handler.

    Apr 16, 2006 12:03:19 AM LogToFile main
    INFO: A message logged to the file
    
    <?xml version="1.0" encoding="windows-1252" standalone="no"?>
    <!DOCTYPE log SYSTEM "logger.dtd">
    <log>
    <record>
    <date>2006-04-16T00:03:19</date>
    <millis>1145163799389</millis>
    <sequence>0</sequence>
    <logger>LogToFile</logger>
    <level>INFO</level>
    <class>LogToFile</class>
    <method>main</method>
    <thread>10</thread>
    <message>A message logged to the file</message>
    </record>
    </log>
    
    Apr 16, 2006 12:03:19 AM LogToFile main
    INFO: A message logged to the file
    

    Multiple Handlers

    You can register multiple handlers with each Logger object. When a logging request comes to the Logger, it notifies all the handlers that have been registered with it, as long as the logging level for the Logger is greater than or equal to that of the logging request. Each handler, in turn, has its own logging level; if the level of the LogRecord is greater than or equal to the level of the handler, then that handler publishes the record.

    MultipleHandlers.java

    Apr 15, 2006 9:32:48 PM MultipleHandlers main
    WARNING: Output to multiple handlers
    Apr 15, 2006 9:32:48 PM MultipleHandlers main
    WARNING: Output to multiple handlers
    Apr 15, 2006 9:32:48 PM MultipleHandlers main
    WARNING: Output to multiple handlers
    

    The console output occurs twice; that�s because the root logger�s default behavior is still enabled. If you want to turn this off, make a call to setUseParentHandlers(false)


    Writing Custom Handlers

    You can write custom handlers by inheriting from the Handler class. To do this, you must not only implement the publish() method (which performs the actual reporting), but also flush( ) and close( ), which ensure that the stream used for reporting is properly cleaned up.

    CustomHandler.java

    Object constructed (ArrayList) by the custom handler:

    [WARNING:, CustomHandler:, main:, , 
    , INFO:, CustomHandler:, main:, , 
    ]
    

    Console handler output:

    Apr 15, 2006 9:58:35 PM CustomHandler main
    WARNING: Logging Warning
    Apr 15, 2006 9:58:35 PM CustomHandler main
    INFO: Logging Info
    

    Formatters

    A Formatter is a way to insert a formatting operation into a Handler�s processing steps. If you register a Formatter object with a Handler, then before the LogRecord is published by the Handler, it is first sent to the Formatter. After formatting, the LogRecord is returned to the Handler, which then publishes it.

    As with Handlers, it is fairly straightforward to develop new Formatters. Extend the Formatter class and override format(LogRecord record). Then, register the Formatter with the Handler by using the setFormatter() call.

    SimpleFormatterExample.java

    Sending email to report log messages:

    EmailLogger.java


    Filters

    To send a logging message to a Logger object, you have to decide what level the logging message should be (the logging API certainly allows you to devise more complex systems wherein the level of the message can be determined dynamically, but this is less common in practice). The Logger object has a level that can be set so that it can decide what level of message to accept; all others will be ignored. This is a basic filtering functionality.

    More sophisticated filtering is needed so that you can decide whether to accept or reject a message based on something more than just the current level. Use custom Filter objects for this. Filter is an interface that has a single method, boolean isLoggable(LogRecord record), which decides whether or not this particular LogRecord is interesting enough to report.

    Once you create a Filter, you register it with either a Logger or a Handler by using the setFilter( ) method.

    SimpleFilter.java


    Log Manager

    The LogManager object is created during class initialization and cannot subsequently be changed. It  keeps track of global logging information. This includes:

    There is a single LogManager object that can be retrieved using the static LogManager.getLogManager method. This is created during LogManager initialization, based on a system property. This property allows container applications (such as EJB containers) to substitute their own subclass of LogManager in place of the default class.


    Controlling Logging Levels through Namespaces

    It�s advisable to give a logger the name of the class in which it is used. This allows you to manipulate the logging level of groups of loggers that reside in the same package hierarchy, at the granularity of the directory package structure.

    LoggingLevelManipulation.java

    LoggingLevelManipulation.txt


    Configuration

    The logging configuration can be initialized using a logging configuration file that will be read at startup. This logging configuration file is in standard java.util.Properties format.

    Alternatively, the logging configuration can be initialized by specifying a class that can be used for reading initialization properties. This mechanism allows configuration data to be read from arbitrary sources, such as LDAP. JDBC, etc.

    There is a small set of global configuration information. This is specified in the description of the LogManager class and includes a list of root-level Handlers to install during startup. The initial configuration may specify levels for particular loggers. These levels are applied to the named logger and any loggers below it in the naming hierarchy. The levels are applied in the order they are defined in the configuration file.

    The initial configuration may contain arbitrary properties for use by Handlers or by subsystems doing logging. By convention these properties should use names starting with the name of the handler class or the name of the main Logger for the subsystem.

    The default configuration makes only limited use of disk space. It doesn't flood the user with information, but does make sure to always capture key failure information.

    The default configuration establishes a single handler on the root logger for sending output to the console. Programmers can update the logging configuration at run time in a variety of ways:

    sorcer.logging

    The preceding configuration file generates rotating log files, which are used to prevent any log file from becoming too large. By setting the FileHandler.limit value, you give the maximum number of bytes allowed in one log file before the next one begins to fill. FileHandler.count determines the number of rotating log files to use; the configuration file shown here specifies three files. If all three files are filled to their maximum, then the first file begins to fill again, overwriting the old contents.

    Alternatively, all the output can be put in a single file by giving a FileHandler.count value of one.

    In order for a program to use the preceding configuration file, you must specify the parameter java.util.logging.config.file on the command line:

    java -Djava.util.logging.config.file=sorcer.logging sorcer.core.util.Log

    Log.java


    Security

    The principal security requirement is that untrusted code should not be able to change the logging configuration. Specifically, if the logging configuration has been set up to log a particular category of information to a particular Handler, then untrusted code should not be able to prevent or disrupt that logging.

    A new security permission LoggingPermission is defined to control updates to the logging configuration.

    Trusted applications are given the appropriate LoggingPermission so they can call any of the logging configuration APIs. Untrusted applets are a different story. Untrusted applets can create and use named Loggers in the normal way, but they are not allowed to change logging control settings, such as adding or removing handlers, or changing log levels. However, untrusted applets are able to create and use their own "anonymous" loggers, using Logger.getAnonymousLogger. These anonymous Loggers are not registered in the global namespace and their methods are not access-checked, allowing even untrusted code to change their logging control settings.

    The logging framework does not attempt to prevent spoofing. The sources of logging calls cannot be determined reliably, so when a LogRecord is published that claims to be from a particular source class and source method, it may be a fabrication. Similarly, formatters such as the XMLFormatter do not attempt to protect themselves against nested log messages inside message strings. This, a spoof LogRecord might contain a spoof set of XML inside its message string to make it look as if there was an additional XML record in the output.

    In addition, the logging framework does not attempt to protect itself against denial of service attacks. Any given logging client can flood the logging framework with meaningless messages in an attempt to conceal some important log message. 


    Logging Practices

    Sometimes there is a requirement to execute some code to perform initialization activities such as adding Handlers, Filters, and Formatters to loggers. This can be achieved by setting the config property in the logging properties file. You can have multiple classes whose initialization can be done using the config property. These classes should be specified using space-delimited values like this:


    config = ConfigureLogging1 ConfigureLogging2 Bar Baz

    Classes specified in this fashion will have their default constructors invoked.


    Bugzilla - Bug-Tracking System