Introduction
Instead of using System.err.println()
to write error messages to the system console, JDK 1.4 provides a logging framework in package java.util.logging
. The logging API allows you to write different log messages (such as error, informational, and configuration messages) to a central location (such as rotating logs) and configure the format and what level of messages are to be written.
Getting Started with Examples
Example 1: Simple Use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.util.logging.*; public class TestSimpleLogger { // Invoke the factory method to get a new Logger or return the existing Logger // of the fully-qualified class name. // Set to static as there is one logger per class. private static final Logger logger = Logger.getLogger(TestSimpleLogger.class.getName()); public static void main(String[] args) { logger.info("Logging begins..."); // log INFO-level message try { // Simulating an Exception throw new Exception("Simulating an exception"); } catch (Exception ex){ logger.log(Level.SEVERE, ex.getMessage(), ex); } logger.info("Done..."); } } |
The outputs of the program is as follow. There are 3 log records. Each log record consists of the time-stamp, fully-qualified class name, method name and the message, and optional exception stack trace.
Nov 15, 2012 3:59:09 PM TestSimpleLogger main INFO: Logging begins... Nov 15, 2012 3:59:09 PM TestSimpleLogger main SEVERE: Simulating an exception java.lang.Exception: Simulating an exception at TestSimpleLogger.main(TestSimpleLogger.java:13) Nov 15, 2012 3:59:09 PM TestSimpleLogger main INFO: Done...
Dissecting the Program
- The main entity of the logging framework is the
Logger
class (in packagejava.util.logger
), on which your applications make logging calls. - private static final Logger logger = Logger.getLogger(TestSimpleLogger.class.getName());
To obtain aLogger
, use thestatic
factory methodLogger.getLogger(String loggerName)
. This method creates a newLogger
or returns the existingLogger
of the givenloggerName
. We typically use the fully-qualified class name (obtained viaClassName.class.getName()
) as the logger's name. The logger is declaredstatic
as there is typically one logger per class. - We can control the output via the so-called Logging Level (in class
Level
of packagejava.util.logging
). The pre-defined levels in descending order of severity are:SEVERE
,WARNING
,INFO
,CONFIG
,FINE
,FINER
,FINEST
. - logger.info(...);
To write a log record of a particular level, you can use the convenient methods:severe(msg)
,warning(msg)
,info(msg)
,config(msg)
,fine(msg)
,finer(msg)
,finest(msg)
of aLogger
object. - logger.log(Level.SEVERE, "Exception", ex);
To log anException
object, you could uselog(level, msg, exception)
method. The above example throws anException
in thetry
clause and logs theException
in thecatch
clause. - By default, the logger outputs log records of level
INFO
and above (i.e.,INFO
,WARNING
andSEVERE
) to standard error stream (System.err
). - You can redirect the
System.err
to a file viaSystem.setErr()
as follows:PrintStream outPS = new PrintStream( new BufferedOutputStream( new FileOutputStream("out.log", true))); // append is true System.setErr(outPS); // redirect System.err
Example 2: Log to File via Logger's File Handler
Instead of logging to the System.err
, you can also log to a file (or an OutputStream
, or network socket, or memory buffer).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.io.IOException; import java.util.logging.*; public class TestFileLogger { private static final Logger logger = Logger.getLogger(TestFileLogger.class.getName()); public static void main(String[] args) throws IOException { // Construct a default FileHandler. // "%t" denotes the system temp directory, kept in environment variable "tmp" Handler fh = new FileHandler("%t/test.log", true); // append is true // fh.setFormatter(new SimpleFormatter()); // Set the log format // Add the FileHandler to the logger. logger.addHandler(fh); // Set the logger level to produce logs at this level and above. logger.setLevel(Level.FINE); try { // Simulating Exceptions throw new Exception("Simulating an exception"); } catch (Exception ex){ logger.log(Level.SEVERE, ex.getMessage(), ex); } logger.info("This is a info-level message"); logger.config("This is a config-level message"); logger.fine("This is a fine-level message"); logger.finer("This is a finer-level message"); logger.finest("This is a finest-level message"); // below the logger's level fh.flush(); fh.close(); } } |
Dissecting the Program
- private static final Logger logger = Logger.getLogger(TestFileLogger.class.getName());
Again, we invoke the factory method to obtain aLogger
with the fully-qualified class name. This method creates a newLogger
or returns an existingLogger
. - Handler fh = new FileHandler("%t/test.log");
logger.addHandler(fh);
To log a a file, allocate aFileHandler
given a log-file name (with "%t
" denotes the system temporary directory retrieved from the environment variableTEMP
orTMP
). Attach theFileHandler
to the logger. - logger.setLevel(Level.FINER);
You can set the level for the logger to discard messages below this level. You can useLevel.ALL
to log all levels andLevel.OFF
to discard all messages. - Run the program and notice that there are two logs: the default
ConsoleHandler
(System.err
) containing messages with levelINFO
and above; and theFileHandler
containing messages with levelFINER
and above. The records inFileHandler
is kept in XML format, e.g.,<record> <date>2011-11-25T15:18:50</date> <millis>1322205530355</millis> <sequence>0</sequence> <logger>TestFileLogger</logger> <level>SEVERE</level> <class>TestFileLogger</class> <method>main</method> <thread>1</thread> <message>java.lang.Exception: Simulating an exception</message> <exception> <message>java.lang.Exception: Simulating an exception</message> <frame> <class>TestFileLogger</class> <method>main</method> <line>21</line> </frame> </exception> </record>
To use simple text format instead of the XML format for the file handler, uncomment this line to set theFormatter
:fh.setFormatter(new SimpleFormatter());
Example 4: Redirecting System.out and System.err to Log File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import java.io.*; import java.util.logging.*; public class TestFileLoggerRedirect { private static final Logger logger = Logger.getLogger(TestFileLoggerRedirect.class.getName()); public static void main(String[] args) throws IOException { // Construct a default FileHandler. Handler fh = new FileHandler("test.log", true); // append is true fh.setFormatter(new SimpleFormatter()); // Set the log format // Add the FileHandler to the logger. logger.addHandler(fh); // Set the logger level to produce logs at this level and above. logger.setLevel(Level.FINE); // Redirecting System.out and System.err PrintStream outPS = new PrintStream( new BufferedOutputStream( new FileOutputStream("test.log", true))); // append is true System.setErr(outPS); // redirect System.err System.setOut(outPS); try { // Simulating Exceptions throw new Exception("Simulating an exception"); } catch (Exception ex){ logger.log(Level.SEVERE, ex.getMessage(), ex); } logger.info("This is a info-level message"); logger.config("This is a config-level message"); logger.fine("This is a fine-level message"); logger.finer("This is a finer-level message"); logger.finest("This is a finest-level message"); // below the logger's level System.out.println("Writing to System.out"); System.err.println("Writing to System.err"); } } |
Example 3: Using a Set of Rotating Log Files
Read HERE.
There is no reason to use System.err.println()
if logging is enabled, unless they are issued thru legacy software. You can redirect System.err
and System.out
to the log 0 (active log) for rotating log.
Logging Framework Explained
Key Classes in java.util.logging
The key elements in java.util.logger
are:
Class Logger
The main entity on which your applications make logging calls. Loggers are normally named based on the fully-qualified class name and organized in a hierarchical manner. To obtain a Logger
, invoke the static
factory method Logger.getLogger(String loggerName)
. This method will create a new Logger
or return an existing Logger
for the given loggerName
. Since we typically use one logger per class, we assign it as a static
variable of the class.
Class Level
A set of standard logging levels that can be used to control the output. You can configure your application to output log messages of a certain level and above, while discarding those below this level. The levels (defined in static constants of class Level
) in descending order of severity are:
Level.SEVERE
: a serious failure, which prevents normal execution of the program, for end users and system administrators.Level.WARNING
: a potential problem, for end users and system administrators.Level.INFO
: reasonably significant informational message for end users and system administrators.Level.CONFIG
: hardware configuration, such as CPU type.Level.FINE
,Level.FINER
,Level.FINEST
: three levels used for providing tracing information for the software developers.
In the logging API, these pre-defined levels are assigned an integer value, e.g., SEVERE
is 1000 and FINEST
is 300.
In addition, Level.OFF
(with value of Integer.MAX_VALUE
) turns off logging and Level.ALL
(with value of Integer.MIN_VALUE
) logs all levels of messages. You can theoretically use custom integer value in your level.
You can set the logging level in a configuration file. You can also set the logging level dynamically via the Logger.setLevel()
method. For example,
// Output log messages of INFO and above
aLogger.setLevel(Level.INFO);
Logging Methods
The Logger
class provides a set of convenience method for writing log message for each of the logging levels:
public void severe(String msg) public void warning(String msg) public void info(String msg) public void config(String msg) public void fine(String msg) public void finer(String msg) public void finest(String msg)
These methods forward the given message to all the registered output handlers if the logger is currently enabled for the given logging level.
You can also use the general log()
, logp()
and logrb()
methods to produce log records:
// Forward the message to all the output handlers if the logger is enabled for the level public void log(Level level, String msg) // Log Precise: similar to log() but specified explicit source class and method names public void logp(Level level, String sourceClass, String sourceMethod, String msg) // Log with ResourceBundle public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msgKey)
Class Handler
Each logger can have access to one or more handlers. The Logger forwards LogRecord
s (on or above the logger's level) to all its registered handlers. The handler exports them to an external device. You can also assign a Level
to a handler to control the outputs. The following handlers are provided:
ConsoleHandler
: for writing toSystem.err
.StreamHandler
: for writing to anOutputStream
.FileHandler
: for writing to either a single log file, or a set of rotating log files.SocketHandler
: for writing to a TCP port.MemoryHandler
: for writing to memory buffers.
Class Formatter
Once the Handler
decides to publish a LogRecord
(i.e., it meets the Level
and Filter
check). It passes the LogRecord
to the Formatter
attached to format the LogRecord
into a String
. The available Formatter
s are SimpleFormatter
(for text message) and XMLFormatter
(XML output). You can also build your own custom Formatter
.
The output of SimpleFormatter
consists of time-stamp, class name, method name, level, message and possible exception (as in Example 1):
Nov 25, 2011 2:05:48 PM com.mytest.SimpleUse doSomething
SEVERE: java.lang.Exception: Simulating an exception
java.lang.Exception: Simulating an exception
at com.mytest.SimpleUse.doSomething(SimpleUse.java:18)
at com.mytest.SimpleUse.main(SimpleUse.java:26)
A sample output of XMLFormatter
is (Example 2):
<record> <date>2011-11-25T15:18:50</date> <millis>1322205530355</millis> <sequence>0</sequence> <logger>TestFileLogger</logger> <level>SEVERE</level> <class>TestFileLogger</class> <method>main</method> <thread>1</thread> <message>java.lang.Exception: Simulating an exception</message> <exception> <message>java.lang.Exception: Simulating an exception</message> <frame> <class>TestFileLogger</class> <method>main</method> <line>21</line> </frame> </exception> </record>
Class Filter
Besides the Level
, you can attach a Filter
to a Logger
or Handler
to filter out the log messages for fine-grain control. To create a Filter
, you implement the Filter interface, and override the isLoggable(LogRecord)
boolean method to decide if a given LogRecord
should be published.
interface Filter {
// Returns true if the LogRecord is to be published.
boolean isLoggable(LogRecord record);
}
Hierarchy of Loggers and Passing of LogRecords to Handlers
Loggers are organized in a hierarchical tree structure, with the root
logger denoted by ""
(empty string). Loggers are typically named based on the fully-qualified class name (e.g., com.mytest.SimpleUse
) or package name (e.g., com.mytest
). Take note that the hierarchy is artificially and strangely arranged based on the dot. That is, com.mytest.SimpleUse
is the parent of com.mytest.SimpleUse.Something
, although the later does not make sense in Java class hierarchy!
If you did not set the level of a logger (or set the level to null
), it inherits the level from its immediate parent.
By default, the root
logger (""
) has a ConsoleHandler
that writes to System.err
. Again, by default, the ConsoleHandler
has a level of INFO
, no Filter
, with SimpleFormatter
; the root logger also has a level of INFO
.
When a logger decides to produce a LogRecord
, the logger not only passes the LogRecord
to all its attached Handler
(s), but it also forwards the LogRecord
to its parent logger. The parent logger forwards the LogRecord
to all its attached Handler
(s) and its parent, without performing level or filter check. Eventually, the LogRecord
reaches the root
logger. By default, the root
logger has a ConsoleHandler
(that writes to System.err
) with level of INFO
.
In Example 1,
- We did not create any handler. The
LogRecord
s are forwarded to theroot
logger's defaultConsoleHandler
. - Try cutting some
FINE
,FINER
logs. They will not be produced, as theConsoleHandler
's level isINFO
. - We did not set the level of the logger. It inherits the level from its nearest parent (which is
root
, of default level ofINFO
). - Try setting the level of this logger lower than
INFO
. It has no effect as theConsoleHandler
has a higher level. - Try setting the level of this logger higher than
INFO
(saysWARNING
). This takes effect.
In example 2,
- The
LogRecord
s are again forwarded to the root logger's defaultConsoleHandler
. - In addition, we attach a
FileHandler
to the current logger. By default, theFileHandler
has level ofALL
, noFilter
, withXMLFormatter
.private static final Logger logger = Logger.getLogger(SimpleUseFile.class.getName()); ...... Handler fh = new FileHandler("%t/test.log"); logger.addHandler(fh); // attach the handler to the current logger
- Instead, it is common to attach the
FileHandler
to theroot
logger. In this way, theFileHandler
will be available to all the loggers in the system.Handler fh = new FileHandler("%t/test.log"); // Add the FileHandler to the root ("") logger, which is the ancestor of all loggers. Logger.getLogger("").addHandler(fh);
Example: The following program prints information about loggers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.mytest; import java.util.logging.*; public class PrintLoggerInfo { private static final Logger logger = Logger.getLogger(PrintLoggerInfo.class.getName()); public static void main(String[] args) { System.out.println("This logger's level is " + logger.getLevel()); // null System.out.println("This logger's filter is " + logger.getFilter()); // null System.out.println("Parent class is " + logger.getParent()); // RootLogger System.out.println("Parent classname is " + logger.getParent().getName()); // "" Logger root = Logger.getLogger(""); System.out.println("Root logger's level is " + root.getLevel()); // INFO System.out.println("Root logger's filter is " + root.getFilter()); // null Handler[] handlers = root.getHandlers(); for (Handler h : handlers) { System.out.println("Handler is " + h); // ConsoleHandler System.out.println("Handler's level is " + h.getLevel()); // INFO System.out.println("Handler's filter is " + h.getFilter()); // null System.out.println("Handler's formatter is " + h.getFormatter()); // SimpleFormatter } } } |
Configuring Logging
Class LogManager
There is a global LogManager
responsible for creating and manager the loggers and maintaining the configuration.
The default configuration establishes a single ConsoleHandler
with level of INFO
on the root
logger for sending output to System.err
.
Configuration File
[TODO]
Using a Set of Rotating Log Files
To create a set of rotating log files, use one of the following constructors to construct a FileHandler
:
FileHandler(String filename, int fileSizeLimit, int fileCount) // Initialize a FileHandler to write to a set of rotating log files. // fileSizeLimit: number of bytes // fileCount: number of files FileHandler(String filename, int fileSizeLimit, int fileCount, boolean append) // with an append flag (true for append and false for override).
The filename shall consist of %g
, which denotes the generation number. For example, "MyApp%g.log
" with file count of 5, will create 5 log files, from MyApp0.log
to MyApp4.log
. As each file reaches (approximately) the given file-size limit, it is closed, rotated out, and a new file opened. The files are renamed as "0", "1", "2", and so on, where "0" is the currently-used log file, and the highest number is the oldest one.
By default, the FileHandler
has level of Level.ALL
, no Filter
, using XMLFormatter
.
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.mytest; import java.io.IOException; import java.util.logging.*; public class TestRotatingFiles { // Invoke the factory method to a new Logger or return the existing Logger private static final Logger logger = Logger.getLogger(TestRotatingFiles.class.getName()); public static void main(String[] args) throws IOException { // Construct a set of rotating log files. // "%t" denotes the system temp directory. // "%g" denotes the generation number. // File size is 1024 bytes (for testing) // Number of files is 3, from 0 to 2. // Append is true (not overriding). Handler files = new FileHandler("%t/test%g.log", 1024, 3, true); // Use text formatter instead of default XML formatter // Default level is ALL, no Filter. files.setFormatter(new SimpleFormatter()); // Add the FileHandler to the root logger. Logger.getLogger("").addHandler(files); // Start logging for (int i = 0; i < 100; ++i) { logger.info("Testing log message " + i); } } } |
Three log files, test0.log
, test1.log
and test2.log
are created in the system temporary directory (based on the environment variable TEMP
or TMP
), with test0.log
as the current log file and test2.log
as the oldest.
To detach the default console handler (which logs INFO-level and above):
// Remove the default console handler
Logger parentLogger = Logger.getLogger("");
Handler[] handlers = parentLogger.getHandlers();
for (Handler handler : handlers) {
parentLogger.removeHandler(handler);
}
Localization via ResourceBundle
Each logger may be associated with a ResourceBundle
for mapping into localized message strings. You can use the method logrb()
to create a log entry with a ResourceBundle
.
Example: [TODO]
Writing Custom Formatter
To write your custom Formatter
, you extend from the base class java.util.logging.Formatter
, and override the format()
method. You may also override the getHead()
and getTail()
methods to wrap head and tail strings around a set of formatted records, e.g., XML starting and ending elements. You could use the formatMessage()
method which returns a localized string based on the LogRecord
and possibly a ResourceBundle
provided in the logrb()
call.
abstract String format(LogRecord record) // Format each LogRecord - to be overridden in subclass String getHead(Handler h) // Called when the handler first use this formatter String getTail(Handler h) // Called before this formatter is closed String formatMessage(LogRecord record) // Format into a localized string
Example
MyHtmlFormatter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
package com.mytest; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.*; /** * Create a custom HTML logging Formatter to show the entries * in a table with 3 columns: level, time-stamp and message. */ public class MyHtmlFormatter extends Formatter { // This method is called for every log records to be published. // Write an HTML row, with cells level, time-stamp and message @Override public String format(LogRecord record) { StringBuilder buf = new StringBuilder(); buf.append("<tr>"); // Level - Show in red for levels on or above WARNING if (record.getLevel().intValue() >= Level.WARNING.intValue()) { buf.append("<td style='color:red'>"); buf.append(record.getLevel()); } else { buf.append("<td>"); buf.append(record.getLevel()); } buf.append("</td>"); // Time stamp buf.append("<td>"); buf.append(formatDateTime(record.getMillis())); buf.append("</td>"); // Message buf.append("<td>"); buf.append(formatMessage(record)); buf.append("</td>"); buf.append("</tr>"); return buf.toString(); } // Called when the handler opens the formatter - Write HTML starting @Override public String getHead(Handler h) { return "<html><body><h2>Log Entries</h2><table border='1'>" + "<tr><th>Level</th><th>Time</th><th>Message</th></tr>"; } // Called when the handler closes the formatter - Write HTML ending @Override public String getTail(Handler h) { return "</table></body></html>"; } // Helper method to format the time-stamp private String formatDateTime(long millisecs) { SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss"); Date recordDate = new Date(millisecs); return dateFormat.format(recordDate); } } |
TestMyHtmlFormatter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.mytest; import java.io.IOException; import java.util.logging.*; public class TestMyHtmlFormatter { private final static Logger logger = Logger.getLogger(TestMyHtmlFormatter.class.getName()); // Create a FileHandler and attach it to the root logger. // Create a MyHtmlFormatter and attach to the FileHandler. public static void setupLogger() throws IOException { Logger rootLogger = Logger.getLogger(""); Handler htmlFileHandler = new FileHandler("%t/log.html"); Formatter htmlFormatter = new MyHtmlFormatter(); rootLogger.addHandler(htmlFileHandler); htmlFileHandler.setFormatter(htmlFormatter); } public void writeLog() { logger.setLevel(Level.ALL); logger.severe("This is a SEVERE-level log"); logger.warning("This is a WARNING-level log"); logger.info("This is a INFO-level log"); logger.finest("This is a FINEST-level log"); try { // Simulating Exceptions throw new Exception("Simulating an exception"); } catch (Exception ex){ logger.log(Level.SEVERE, ex.getMessage(), ex); } } public static void main(String[] args) throws IOException { setupLogger(); TestMyHtmlFormatter m = new TestMyHtmlFormatter(); m.writeLog(); } } |
printStackTrace()
Older Java programs tend to use printStackTrace()
in the catch
clause to print the stack trace to the System.err
, as follows:
try { ...... } catch (Exception ex) { // Print the stack trace to System.err ex.printStackTrace(); }
The printStackTrace()
, which is not thread-safe, is nowadays considered a bad practice and should not be used on the production codes. It is highly recommended to either remove the printStackTrace()
from production code, or replaced by a more robust and powerful logging facility (such as Java Logging Framework or Log4j).
For example, you can use the Java logging framework as follows to replace the printStackTrace()
.
public class MyClassName { private static final Logger logger = Logger.getLogger(MyClassName.class.getName()); ..... // Within a method try { ...... } catch (Exception ex) { // Log the stack trace logger.log(Level.SEVERE, ex.getMessage(), ex); } ..... }
Why printStackTrace() is bad?
- The
printStackTrace()
method is not thread-safe (and you can't synchronize onSystem.err
andSystem.out
). In other words, a multi-threaded application will likely interleave the stack traces and produce very confusing log entries. For agent applications, the stack traces could dirty the user prompt. On the other hand, Logger's methods are thread-safe. For example, althoughConsoleHandler
andStreamHandler
publish toSystem.err
, but the operation is synchronized - every thread that attempts to publish a log entry must acquire the lock on the monitor associated with theStreamHandler
instance. - Stack traces are meant for debugging by the developers during the development, not for end users in production. They should therefore be directed to a log file instead of console. End users shall not be exposed to the stack traces.
- Redirecting
System.err
to a file (viaSystem.setErr()
) may not solve the problem, because the log file could be huge. You need a set of rotating log files. - Logging facilities are far more powerful than the simple
printStackTrace()
! You can control the amount of outputs produced. You can direct the outputs to console, file (including rotating log files), memory, and even network (send to a remote host or trigger a email?!). - Stack trace, by itself, may not be sufficient to debug the program. Logging facility could be used to log additional information, system configuration, and program traces.
Log4j
Apache's Log4j is a popular Java logging framework, available @ http://logging.apache.org.
To create a logger:
import org.apache.log4j.Logger; ...... private static final Logger logger = Logger.getLogger(MyClassName.class);
[TODO]
REFERENCES
- Java Logging Overview @ http://docs.oracle.com/javase/1.4.2/docs/guide/util/logging/overview.html.