Server side logging and debugging is implemented through Java classes located
in the package
ifs.fnd.log
. The classes are included in the library file
ifs-fnd-common.jar.
This file doesn’t have any external dependencies and is included in all the Enterprise
Application (ear) files created by the build process, but it can be necessary to include
this file to the class path for compilation of Java classes.
Read more about the concepts and configuration in the Foundation1 Administration Guide – Administrate System Services – Server Logging.
The base concept of the logging framework is Logger. A Logger is an instance that is used for writing output using one of the predefined methods. For a complete list of available methods see the JavaDoc Reference documentation for class ifs.fnd.log.Logger.
A set of static methods for obtaining different Loggers is available through the class
ifs.fnd.log.LogMgr (see the
JavaDoc
Reference for complete list of available methods).
A set of Loggers, one for
each
category, is created per thread basis
and encapsulated in an instance of ThreadLoggers class. A
Logger defines also a set of boolean
flags corresponding to the defined
level.
A Logger instance is immutable, thus level of an existing Logger instance
can not be changed, which can lead to a problem only if global or thread levels have
been changed while a particular Logger instance is still in use. In such situation it can be
necessary to obtain a new Logger instance.
Normally this is not a problem, but in
some cases, for example in stand alone servers, in infinite loops, one should
consider obtaining new Logger instances from time to time instead of using a
locally stored one during the whole time.
On the other hand obtaining of Logger
instances is an operation that takes some time, so if possible it is
recommended to store once fetched Logger instance locally and reuse it as long
as possible.
The framework will discover automatically if prerequisites for a
particular Logger have changed and only create new instances if necessary, so
obtaining a Logger instance through one of the static methods in LogMgr will not
necessary create a new instance each time.
Never store Loggers in static
variables! But you can store a Logger in a member (instance) variable if you
know that your class is short-lived, i.e. is created per request basis and destroyed
as soon as the request has been processed. If you do not deal with threads and
infinite, or at least long-lived loops, it is often a good idea to store a
Logger instance in a local variable in a method, if storing as member
variable in a class is not possible.
First in your code obtain a Logger by calling one of the get
methods in
ifs.fnd.log.LogMgr, e.g.:
Logger log = LogMgr.getApplicationLogger();
There is one get
method for each Logger category.
Then you can use your log
instance to call different logging methods on, depending on logging level and
number of arguments.
There are five groups of methods: error()
, warning()
,
info()
, trace()
and debug()
, one group for each level. Within each group there
are a number of overloaded methods taking an instance of Throwable and/or
message with up to 9 parameters. If using syntax with parameters, parameter
placeholders are denoted by sequences ‘&1’ to ‘&9’ within the message string.
You can send an arbitrary object or type as a parameter and it can be null.
With exception of error()
methods, calls to all other methods have to be preceded by
checking a corresponding boolean flag, i.e.:
log.error(...); if(log.warning) log.warning(...); if(log.info) log.info(...); if(log.trace) log.trace(...); if(log.debug) log.debug(...);
This construction is used to avoid evaluation of possible expressions send to
function call in case the respective level is disabled.
Because error()
methods
are always enabled, there is no corresponding flag for them. Therefore the error()
methods should be used with caution.
Typically application code will use only one Logger instance, but if necessary, it is possible to obtain some of them and store in different variables.
Normally a developer doesn’t need to think about setting levels for different Loggers. Levels are set globally through the configuration. But when developing any kind of framework it can be sometimes necessary to deal with levels. It can be necessary to set/reset global and/or per thread defined levels depending on other configuration than the default one and/or as a response to per request basis level definitions.
For setting of levels there are a number of set
methods in
ifs.fnd.log.LogMgr class. Methods of type
setDefault
are for levels
applicable for the entire Java VM, i.e. have the same function as definitions in
the
configuration files, while the remaining
set
methods are per thread basis.
If it is necessary to set thread levels for more than one Logger category at the
same time, the better way of doing that, instead of calling a number of set
methods from LogMgr, is to call LogMrg.getThreadLoggers()
to obtain a container
instance and then call corresponding set
methods from the class
ifs.fnd.log.ThreadLoggers.
Similarly, if obtaining several Loggers, the best method is to first
fetch a ThreadLoggers container instance and then obtain Loggers from this
container by calling corresponding get
method. This is because each
get
or set
method from LogMgr class has to fetch the ThreadLoggers instance anyway before
obtaining a proper Logger, which takes some CPU time.
If it is necessary to manipulate "per thread" levels, typically at the start
of a request, it can be necessary to reset level values to their initial values
when the request is done. This can be especially important when executing within an
Application Server (or any other framework) that implements any kind of thread
pool where threads can be reused during subsequent calls.
To reset thread levels
to their initial values simply call LogMgr.reset()
method.
Tip: Sometimes you may want to temporarily enable
debugging of the code you’re about to develop without affecting the entire
system. You may want to add some hard coded statements in your code for doing
this, which you will typically remove or comment once you’re done with your
task.
A way of performing this using the new logging framework could be to temporarily
change the logging level of the logger you’re using:
Logger log = LogMgr.getApplicationLogger();
...
//the line below you can remove or comment once you’re done
int currLevel = LogMgr.setApplicationLevel(LogMgr.DEBUG);
...
//here you can have your debug printouts as usual
// - you can left them in the code once done
if(log.debug) log.debug(...);
...
//once finished you have to set back the initial level
// – even this line you’ll remove or comment once your task is finished
LogMgr.setApplicationLevel(currLevel);
A better way to achieve this goal is to use class debugging described
below.
Tag concept makes it possible to tag single messages with an identifier that can be used for filtering the output.
For this purpose there is a class
ifs.fnd.log.Tags that can store one or more string identifiers.
Instances of Tags can be created and stored on any level – from local
variables through member variables to static variables. Typically an instance of
Tags will represent a single identifier, but there are situation where
messages need to be tagged with a couple of tags – then an instance of Tags
can represent a collection of single tags.
For tagging support all methods in ifs.fnd.log.Logger exist in versions taking Tags as argument.
Tag concept is widely used within the IFS Connect framework - logging output is often tagged with the current instance name, i.e.: name of reader, sender, queue or server.
Tag type
A tag can be typed or untyped. A tag type is a string identifier that can be used for grouping and filtering tags. Tag collection doesn't allow duplicated tag names, but two tags with the same name and different types are treated as different.
Sometimes, especially during development, there are needs for additional debug statements that can be very useful during the development cycle, but maybe not so useful later on, in the finished code. Statements that typically write a lot of output to the debug destination.
Usually developers put some additional debugging statements that are commented or removed from the code once the task is done. Statements that, of different reasons, are not necessary even for normal debugging purpose of the finished code later on.
But there can be situations where it would be useful to keep this type of debugging statements in the code anyway with possibility for enabling those if necessary, for example when surrounding a bug.
For similar purposes there is a concept of class debugging. Class debugging is a concept of additional Loggers created per class. Class loggers can be enabled or disabled independently of other Loggers. Class loggers are used for output of class specific debug information that is not interesting during normal debugging of a system flow.
You can obtain an instance of a class logger by calling
LogMgr.getClassLogger(java.lang.Class)
method. When class debugging
is disabled in the
configuration, the call to this function will return a dummy logger, common
to the entire system, that does nothing, except for the error()
method.
Class loggers can be used as ordinary loggers with all the available levels, but most probably a typical usage will involve only 'debug' or maybe also 'trace' levels. Typically, if you want to use the class logger concept, you will have two different loggers in your code: a “normal” logger for one of the ten predefined categories, for example ‘application’, and a second one – class logger for your particular class.
Note that class loggers are referred by names, so this is important to send right instance of java.lang.Class to the method in LogMgr, e.g. if you want to debug all the subclasses using the same class logger – use the supper class when obtaining the logger.
The logging output can be filtered in several different ways: by level, category, but also by assigning specially designed filters to Handlers.
The framework is delivered with three filter implementations: ifs.fnd.log.TagFilter, ifs.fnd.log.TagTypeFilter and ifs.fnd.log.ClassFilter, but if required, new filters can be developed.
To develop a new filter simply extend your new filter class from ifs.fnd.log.IfsFilter. The only method that must be implemented is:
boolean isRecLoggable(java.util.logging.LogRecord)
.
This method returns true if the incoming LogRecord should be passed through the filter, false otherwise.
If your new class is expecting some arguments, you probably have to implement
also method
init()
, where you can read the passed arguments by calling
int getArgumentCount()
and
String getArgument(int index)
. The init()
method is
called only once, when the filter instance is created, while isRecLoggable()
is called for each logging event.
Sometimes you need to write out the current stack trace. For this purpose the
Logger class offers a method with name
debugStackTrace()
. Especially when debugging from within code
executed within an application server, the stack trace can be quite huge - not
seldom several pages with information that is, for most of cases quite
uninteresting. To limit the amount of output this method only prints out lines
from IFS classes. But if it is necessary to print the entire stack, it is possible to
call the
overloaded version of the method taking a boolean as argument telling
if only IFS lines should be printed ('true') or everything ('false').
The stack trace printed using this method is formatted in slightly different way as formatted by standard Java to avoid possible confusions with thrown exceptions.
If of any reason you need to forward everything that is written to a
particular Logger instance within the current thread to another Logger, there is
a mechanism for that. Calling
forward(Logger to)
on a
Logger instance will cause
everything send to this instance within the current thread to be forwarded to the
instance send as method argument - not only from your current code, but
everything from all code executed within the same thread after the
forward()
method has been called. Forwarding can be
stopped by calling method
stopForwarding()
. This is also possible to check if
the current Logger is already forwarded or not by calling
boolean isForwarded()
.
Warning:
This functionality has to be used with caution because threads within an
application server are reused - you must be absolutely sure that the forwarding
is stopped before you leave your code within the current request. Especially
that the mechanism allows nested forwarding!
You can find more information in the JavaDoc Reference documentation for the
Java package
ifs.fnd.log.