Java Logging

Russell Bateman
March 2022

Java Logging

Formally known as Java Logging (or juli in Tomcat), because java.util.logging implementation, is native to Java; there is no need for a third-party JAR.juli is not as nice as Logback, but nothing else really works, especially if you want to be instantly successful and save yourself much trouble, with Tomcat.

Note that slf4j is a facade atop any logging framework (including log4j, logback and even juli).

Configuration

Configuration is done using standard java.util.Properties format, typically in a file named logging.properties, under the classes subdirectory of the JAR or WAR (WEB-INF/classes). For example, in the IDE on the path src/main/resources/logging.properties (and/or src/test/resources/logging-test.properties).

Then, when running inside the IDE, these will just be found and used.

However, when running outside the IDE (i.e.: in actual production), do this:

For host-wide configuration (unlikely), the configuration file can be placed on the path ${JAVA_HOME}/conf.

Sample logging.properties file:
# Support both a console handler (most often seen in IDE) and a file handler
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level--all loggers (not really needed because each handler specifies here)
.level= INFO

# File handler; default output is in user's home directory.
java.util.logging.FileHandler.level     = INFO
java.util.logging.FileHandler.pattern   = %h/java%u.log
java.util.logging.FileHandler.limit     = 50000
java.util.logging.FileHandler.count     = 1
java.util.logging.FileHandler.maxLocks  = 100
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Console handler
java.util.logging.ConsoleHandler.level     = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Customize the SimpleFormatter output format
#     level: log message [date/time]
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

# Constrain logging per package path
com.windofkeltia.utilities.level = SEVERE
com.windofkeltia.parser.level    = INFO
com.windofkeltia.servlet.level   = WARN

Sample usage

package com.windofkeltia.foo;

import java.util.logging.Logger

public class Foo
{
  private static Logger logger = Logger.getLogger( Foo.class.getName() );

  public static void main( String argv[] )
  {
    logger.finest( "doing stuff" );                        // like Logback's logger.trace( "main()" );

    try
    {
      Foo.sneeze();
    }
    catch ( Exception e )
    {
      logger.log( Level.WARNING, "trouble sneezing", e ); // logger.warning( ... );
    }

    logger.fine( "done" );                                // like Logback's logger.debug( "done " );
  }
}

Log levels

Let's compare them to Logback.

JULI Logback
FINEST TRACE
FINER TRACE/DEBUG
FINE DEBUG
INFO INFO
WARNING WARN
SEVERE ERROR
ALL (gets dim for me)
OFF OFF (as I recall)
CONFIG Reveal logging configuration (in the log)

Logging flow control

  1. A logging request of some level is made to the logger attached to the currently executing package.
  2. If the request level is too low for that package's logger, it's discarded.
  3. Otherwise, ...
  4. Cycle through all handlers
    1. if the request level is too low for that handler, discard it,
    2. otherwise log the request.

Strategy

  1. At the top of every class in which code will issue log statements, do like this:
    import java.util.logging.Logger;
    
    public class Foo
    {
      private static final Logger logger = Logger.getLogger( Foo.class.getName() );
      ...
    
  2. Use Level.FINE for anything that aids in debugging:
      logger.log( Level.FINE, "Processing {0} entries in loop", list.size() );
      // or:
      logger.fine( "Processing {0} entries in loop", list.size() );
    
  3. Use Level.FINER or Level.FINEST inside of loops and in places where you may not wish to that much detail very often when debugging, but where you want to see crucial detail on certain occasions:
      logger.log( Level.FINER, "Blah, blah, blah..." );
      // or:
      logger.finer( "Blah, blah, blah..." );
    
  4. Use the parameterized feature of logging calls to avoid String concatenation that will hurt garbage collection as well as slow down processing especially when the log level isn't even set high enough to use the product of a FINER or FINEST level. The parameterization executes inside the call after the level is checked:
      logger.finer( "Processing {0} entries in loop", list.size() );
      // instead of:
      logger.finer( "Processing " + list.size() + entries in loop" );
    
  5. Consider logging the complete details of an exception:
      try
      {
        ..
      }
      catch( Exception e )
      {
        logger.severe( e.toString(), e );
      }
    

    Note: passing e.toString() causes (e.getMessage()) to be put into the log statement on the same line. That way, fgrep'ing for "Exception" will show the message and no advanced regular expression string(d) need be created to match the log statement.

Useful links