Logging with log4j
Basics
Log4j uses a set of hierarchical loggers, which inherit from a root logger. All
logger are identified by their names, which may contain dots to indicate that a logger
inherits from a parent logger. The inheritance includes output format as well as a threshold
value, which limits the amount of messages actually passing the logger.
These log levels are the following:
DEBUG, INFO, WARN, ERROR, FATAL
You set them by using one of the following log statements:
oLogger.debug(String pstrMessage)
oLogger.info(String pstrMessage)
oLogger.warn(String pstrMessage)
oLogger.error(String pstrMessage)
oLogger.fatal(String pstrMessage)
Setting a logger to a threshold of DEBUG means that all of these statements can pass their
message to the logging media; setting it to WARN means, that only the warnings and error
messages can pass the logger and actually be logged.
Using the loggin system in your code
To use the logging system in your code, you have to create one or more instances of
Logger, using the logger name, which may be anyone of the configured loggers,
including any of the child loggers. For this example, we use a logger hierarchy like the
following:
root logs to console and error file, INFO and higher
root.general logs to console and error file, INFO and higher
root.general.config logs to console and error file, INFO and higher
root.algorithmus logs to console and error file, DEBUG and other messages
File logs to sucess file, INFO and higher
We want a general logger (root.general) for all general logging output. The
configuration section of the code should by logged separatedly, so we create another logger
root.general.config for this, which may be configured independently from other
program section, if desired (e.g. switched off later). All these loggers inherit from the
root logger, which is configured to log only informational or errorneous messages.
Furthermore, for implementing the
algorithm, we want another independent logger root.algorithmus, which is clearly
separated from the general logger. This way, we can switch off the complete algorithmic section
of the code later, if it runs errorfree. At the moment, this logger can separatedly
configured to log also debug messages, without touching the general logging section.
Note that we want all of these loggers to log warnings or error messages also in a file
test.err, so we configure these loggers to write to the console as well as a
file.
At last, we want a file logger (File), getting only messages about a
successful run for storing. This logger inherits from the root logger, too, but will be configured
to act independently from the root logger.
Now we will see the test program for the logging facility with heavy
overdue of logging statements:
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class Logtest {
public static void main(String args[]){
Logger oLog = Logger.getLogger("root");
Logger oLogGen = Logger.getLogger("root.general");
Logger oLogCfg = Logger.getLogger("root.general.config");
Logger oLogFileOK = Logger.getLogger("File");
PropertyConfigurator.configure("log.properties");
oLogGen.debug("Starting program");
if (args.length!=1) {
oLogCfg.error("Error: missing parameter");
System.exit(0);
}
int n = 0;
try {
n = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe){
oLogCfg.error("Error: not an integer");
System.exit(0);
}
oLogCfg.info("Running program with n="+n);
if (n>10){
oLogCfg.warn("Parameter limited to 10");
n = 10;
}
oLog.debug("calling function");
long lStart = System.currentTimeMillis();
int erg = getSum(n);
long lEnd = System.currentTimeMillis();
oLog.debug("function call finished successfully");
oLog.info("Result for "+n+" is "+erg);
oLogGen.info("Calculation took "+(lEnd-lStart)+" ms");
oLogFileOK.info("Result for "+n+" is "+erg);
oLogGen.debug("Stopping program");
}
private static int getSum(int piMax){
Logger oLogAlg = Logger.getLogger("root.algorithmus");
oLogAlg.debug("Entering function with param "+piMax);
int erg = 0;
for (int i=0; i<piMax; i++){
oLogAlg.debug("Loop with i="+i+", erg is "+erg);
erg += i;
}
oLogAlg.debug("Returning from function");
return erg;
}
}
|
Configuring the logging system
To achive the desired logging possibilities, we have to configure the root logger
with a threshold of INFO; most of the root... logger, especially the
root.general... logger, inherit this
from the root logger. The root logger writes its output to the console and a file,
using the appenders stdout and fileerr. Only the algorithm
logger must be individually configured to use a threshold of DEBUG.
Because we want only warnings and error messages in the error log file, we set the
threshold for the error file explicitly in the appender section of the
configuration. This gives us the change of changing the threshold only for the file
appender, not for the console output.
The logger for the success file fileok is configured with a threshold of
INFO, but note that the additivity of this logger is set to false. Normally,
this logger inherits from the root logger, too (even if this is not expressed in the
manually given name File, what would cause this logger writing to the
console and the error file, too. To suppress this, we set the additivity to false, meaning
that this logger is independent of the root logger.
Now let have a look at the config file log.properties:
# logger structure:
#
# Logger name Output to console Output to success file Output to err file
# root >=INFO no >=WARN
# root.general >=INFO no >=WARN
# root.general.config >=INFO no >=WARN
# root.algorithmus >=DEBUG no >=WARN
# File no >=INFO no
# basic settings ------------------------
# overall log level is INFO (includes categories root, root.general and
# root.general.config)
# we use the appender named 'stdout' as well as 'fileerr' to log errors
log4j.rootLogger=INFO, stdout, fileerr
# we are interested in the algorithm, so we increase the log level for this
# category to DEBUG
log4j.logger.root.algorithmus=DEBUG
# we need another appender for logging succesful runs in a disk file
# we name it 'fileok'
# the additivity is set to false, because all output for logger 'File' would
# be duplicated in the console output otherwise (because 'File' is inserted
# under the root logger, too)
log4j.logger.File=INFO, fileok
log4j.additivity.File=false
# settings of the root appender ---------------------
# console appender which is named 'stdout'
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c %x -%m%n
# appender which is named 'fileerr'
log4j.appender.fileerr.Threshold=WARN
log4j.appender.fileerr=org.apache.log4j.RollingFileAppender
log4j.appender.fileerr.File=test.err
log4j.appender.fileerr.MaxFileSize=100KB
log4j.appender.fileerr.MaxBackupIndex=1
log4j.appender.fileerr.layout=org.apache.log4j.PatternLayout
log4j.appender.fileerr.layout.ConversionPattern=%d [%t] %-5p %c %x -%m%n
# settings of the file appender ---------------------
# appender which is named 'fileok'
log4j.appender.fileok=org.apache.log4j.RollingFileAppender
log4j.appender.fileok.File=test.log
log4j.appender.fileok.MaxFileSize=100KB
log4j.appender.fileok.MaxBackupIndex=1
log4j.appender.fileok.layout=org.apache.log4j.PatternLayout
log4j.appender.fileok.layout.ConversionPattern=%d [%t] %-5p %c %x -%m%n
|
The output
To give you an impression of how the output of the test program looks like,
here is the console output:
2003-01-01 10:11:21,376 [main] INFO root.general.config -Running program with n=50
2003-01-01 10:11:21,376 [main] WARN root.general.config -Parameter limited to 10
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Entering function with param 10
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=0, erg is 0
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=1, erg is 0
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=2, erg is 1
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=3, erg is 3
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=4, erg is 6
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=5, erg is 10
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=6, erg is 15
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=7, erg is 21
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=8, erg is 28
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Loop with i=9, erg is 36
2003-01-01 10:11:21,386 [main] DEBUG root.algorithmus -Returning from function
2003-01-01 10:11:21,386 [main] INFO root -Result for 10 is 45
2003-01-01 10:11:21,386 [main] INFO root.general -Calculation took 0 ms
Setting the threshold of the algorithm logger to INFO would fade out all debug messages from
this section:
2003-01-01 10:11:21,376 [main] INFO root.general.config -Running program with n=50
2003-01-01 10:11:21,376 [main] WARN root.general.config -Parameter limited to 10
2003-01-01 10:11:21,386 [main] INFO root -Result for 10 is 45
2003-01-01 10:11:21,386 [main] INFO root.general -Calculation took 0 ms
Setting the threshold of the general logger to WARN would further fade out all messages
besides the really basic ones:
2003-01-01 10:11:21,376 [main] WARN root.general.config -Parameter limited to 10
2003-01-01 10:11:21,386 [main] INFO root -Result for 10 is 45
The error log contains entries like these:
2003-01-01 09:37:41,652 [main] ERROR root.general.config -Error: missing parameter
2003-01-01 09:37:51,927 [main] WARN root.general.config -Parameter limited to 10
2003-01-01 09:38:33,927 [main] WARN root.general.config -Parameter limited to 10
2003-01-01 09:38:50,040 [main] WARN root.general.config -Parameter limited to 10
And, finally, the success log is the smallest:
2003-01-01 09:37:51,937 [main] INFO File -Result for 10 is 45
2003-01-01 09:38:33,947 [main] INFO File -Result for 10 is 45
2003-01-01 09:38:50,040 [main] INFO File -Result for 10 is 45
Using Map contexts
In a multi-threaded application, as for example a J2EE application, it is possible that several threads
access the same class at the same time, yielding multiple log entries with the same class as source.
It is then difficult to distinguish the log entries from a single thread from that of another thread without
further information.
It is possible and easy to give the logger additional information to be printed out. Look at the
following example code:
import org.apache.log4j.*;
...
String strUser = <get name of current user (valid for this thread!)>
MDC.put("user", strUser);
logger.debug("entering servlet");
...
logger.debug("do something");
...
logger.debug("exiting servlet");
MDC.remove("user");
The layout pattern for the logger is defined as follows:
%d{ABSOLUTE} %-5p %c{1} [%X{user}] %m%n"
We have used an object from log4j called MDC, which essentially is a map of keys
and values. Here, the key is user and contains the name of the current user of the
servlet. This value is stored in the map and can be used in the logging output, using the
pattern specifier %X{user} (where user may be replaced by whatever your
map key was called). After the block in question, the key is removed from the map.
Now, assuming that the users Ingo and Claudia are accessing the code in two different threads,
we get this logging output:
2003-01-01 09:38:33,947 [main] DEBUG myservlet [ingo] entering servlet
2003-01-01 09:38:33,947 [main] DEBUG myservlet [claudia] entering servlet
2003-01-01 09:38:33,947 [main] DEBUG myservlet [ingo] do something
2003-01-01 09:38:33,947 [main] DEBUG myservlet [ingo] exiting servlet
2003-01-01 09:38:33,947 [main] DEBUG myservlet [claudia] do something
2003-01-01 09:38:33,947 [main] DEBUG myservlet [claudia] exiting servlet
The addition information makes it easy to distinguish the individual threads.
It is easy to log events in JBoss with log4j, because this logging system is already used for internal
JBoss logging. You may use it for yourself by modifying the file log4j.xml in the directory
$jboss/server/default/conf. For this example, we modify the configuration as follows
(note that for JBoss, you have to use the XML version of the configuration file, but in principle,
all remains the same as with the property file format):
... original file content ...
<appender name="MYFILE" class="org.jboss.logging.appender.RollingFileAppender">
<param name="File" value="${jboss.server.home.dir}/log/my.log"/>
<param name="Append" value="false"/>
<param name="MaxFileSize" value="500KB"/>
<param name="MaxBackupIndex" value="1"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c{1}] %m%n"/>
</layout>
</appender>
<appender name="MYCONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
</layout>
</appender>
<logger name="de.kksoftware" additivity="false">
<level value="INFO" />
<appender-ref ref="MYCONSOLE"/>
<appender-ref ref="MYFILE"/>
</logger>
<logger name="de.kksoftware.servlets" additivity="false">
<level value="DEBUG" />
<appender-ref ref="MYCONSOLE"/>
</logger>
<!-- ======================= -->
<!-- Setup the Root category -->
<!-- ======================= -->
<root>
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
... rest of original file follows here ...
|
What we want is logging output for our own classes in a file and the console, too. For productive use, we
want only catch informational (and higher-level) messages. Only for our servlets, we want also the
debugging output, but only logged to the console. The JBoss-specific message should be logged as specified
by the default configuration.
Therefore we have defined two additional appenders MYFILE and MYCONSOLE,
one for logging into a file using INFO as threshold,
the second for the console output. All classes under the package de.kksoftware logs
both into the console and the file, and all servlets from the package de.kksoftware.servlets
log only to the console, having a DEBUG threshold.
You may use the above loggers as usual, an example is given here for a servlet:
package de.kksoftware.servlets;
import org.apache.log4j.*;
public abstract class ExampleServlet extends HttpServlet {
protected Logger m_oLog;
public void init(ServletConfig poConfig) throws ServletException {
super.init(poConfig);
m_oLog = Logger.getLogger(this.getClass().getName());
m_oLog.debug("servlet "+this.getClass().getName()+" initialized");
}
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
...
m_oLog.info("Servlet called");
...
}
Note how the class name is used as logger name while getting a logger instance by simply calling
this.getClass().getName(). (You are still free to specify another name by giving it literally.)
The logging mechanism can be used from JSPs, too, by getting a logger instance as follows (using
de.kksoftware.jsp as the root logger for the JSPs):
<%@ page language="java" session="true" %>
<%@ page import="org.apache.log4j.Logger" %>
<%
Logger oLog = Logger.getLogger("de.kksoftware.jsp.myJspPage");
oLog.debug("query is "+request.getQueryString());
%>
You can see that there is nothing mysterious in logging in JBoss. In fact, it is even more simple than logging
in your own programs, because reading of configuration files is already done by JBoss.
It is also worth to mention that JBoss does look up the configuration file regularly (every one or two minutes) for
modifications. That means, after changing the config file and waiting a little bit, your running J2EE application
uses the new logging settings without restarting it!
back to toolbox page
|