Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
In symbols one observes an advantage in discovery which
is greatest when they express the exact nature of a thing
briefly and, as it were, picture it; then indeed the labor of
thought is wonderfully diminished.
—GOTTFRIED WILHELM LEIBNIZ
Joran stands for a cold north-west wind which, every now and
then, blows forcefully on Lake Geneva. Located right in the middle
of Europe, the Geneva lake happens to be the continent's largest
sweet water reserve.
In the first part, we start by presenting ways for configuring
logback, with many example configuration scripts. In the second part, we present Joran, a generic
configuration framework, that you can put into use in order to
configure your own applications.
Configuration in logback
Inserting log requests into the application code requires a
fair amount of planning and effort. Observation shows that
approximately four percent of code is dedicated to
logging. Consequently, even moderately sized applications will
contain thousands of logging statements embedded within its
code. Given their number, we need powerful tools to manage these
log statements.
Logback can be configured either programmatically or with
configuration a script (expressed in XML format). By the way,
existing log4j users can convert their log4j.properties
files to logback.xml using our PropertiesTranslator
web-application.
Let us begin by discussing the initialization steps that
logback follows to try to configure itself:
Logback tries to find a file called logback-test.xml
in the classpath.
If no such file is found, it checks for the file
logback.xml.
In case neither file is found, logback configures itself
automatically using the BasicConfigurator
which will cause logging output to be directed on the
console.
The third and last step is meant to provide a default (but very
basic) logging functionnality in the absence of a configuration
file.
Assuming the logback-test.xml file is placed under
src/test/resources folder, Maven will ensure that it
won't be included in the artifact produced. Thus, you can use a
different configuration file, namely logback-test.xml
during testing, and another file, namely, logback.xml, in
production. The same principle could apply by analogy for Ant.
Automatically configuring logback
The simplest way to configure logback is by letting logback
fallback to its default configuration. Let us give a taste of how
this is done in an imaginary application called
MyApp1.
package chapter3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
static final Logger logger = LoggerFactory.getLogger(Foo.class);
public void doIt() {
logger.debug("Did it again!");
}
}
In order to run the examples in this chapter, you need to make
sure that certain jar files are present on the classpath. Please
refer to the setup page for further
details.
Assuming the configuration files logback-test.xml or
logback.xml could not be found, logback will default to a
minimal configuration hardwired to attaching a
ConsoleAppender to the root logger. The output is
formatted using a PatternLayout set to the pattern
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -
%msg%n. Moreover, by default the root logger is assigned to
the DEBUG level.
Thus, the output of the command java chapter3.MyApp1
should be similar to:
16:06:09.031 [main] INFO chapter3.MyApp1 - Entering application.
16:06:09.046 [main] DEBUG chapter3.Foo - Did it again!
16:06:09.046 [main] INFO chapter3.MyApp1 - Exiting application.
The MyApp1 application links to logback through
calls org.slf4j.LoggerFactory and
org.slf4j.Logger classes, retrieve the loggers it
wishes to use, and chugs on. Note that the only dependence of the
Foo class on logback are through
org.slf4j.LoggerFactory and
org.slf4j.Logger imports. Except code that configures
logback (if such code exists) client code does not need to depend
on logback. Given that SLF4J permits the use of any implementation
under its abstraction layer, it is rather easy to migrate large
bodies of code from one logging system to another.
The same with JoranConfigurator
The previous example outputs logging information always in the
same fixed manner. How boring! It takes few easy steps to modify
MyApp1 so that the log output can be controlled at
runtime. Here is a slightly modified application called
MyApp2.
package chapter3;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
public class MyApp2 {
final static Logger logger = LoggerFactory.getLogger(MyApp2.class);
public static void main(String[] args) {
// assume logback is in use
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.shutdownAndReset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
StatusPrinter.print(lc);
}
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
}
MyApp2 fetches the LoggerContext
currently in effect, creates a new JoranConfigurator,
gives it the context and finally asks that the configurator parses a
configuration file. A basic configuration file that creates the same
logging environment as would the default configuration is listed
below:
Example 3.3: Basic configuration file
(logback-examples/src/main/java/chapter3/sample0.xml)
The above configuration file directs logback to create an
ConsoleAppender named STDOUT whose output is
format is managed by a PatternLayout. The root logger
is configued by setting its level to DEBUG and attaching it the
previously created ConsoleAppender.
Note we could have omitted setting the level of the root logger
to DEBUG because that is its default value.
Assuming the current directory is logback-examples, try
running the MyApp2 class by issuing the following
command:
The ouput of this command is very similar to the output of the
previous example, except that MyApp2 retrieves a
logger called chapter3.MyApp2 instead of
chapter3.MyApp1. The output will reflect the
difference.
16:09:00.593 [main] INFO chapter3.MyApp2 - Entering application.
16:09:00.593 [main] DEBUG chapter3.Foo - Did it again!
16:09:00.593 [main] INFO chapter3.MyApp2 - Exiting application.
It is often very useful to define the logback debug
configuration property in order to instruct logback to output
internal configuration messages on the console. To achieve this,
one only needs to add an attribute to the main
configuration element in the configuration file, as shown
above:
wec
Example 3.4: Basic configuration file using debug mode (logback-examples/src/main/java/chapter3/sample1.xml)
This should cause logback to print internal configuration
messages in addition to the actual logs. Relaunching the
MyApp2 application with this new configuration file
will ouput the following lines:
|-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch. \
qos.logback.core.ConsoleAppender]
|-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
|-INFO in ch.qos.logback.core.joran.action.AppenderAction - Popping appender named [STDOUT] from the \
object stack
|-INFO in ch.qos.logback.classic.joran.action.LevelAction - root level set to DEBUG
|-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to \
Logger[root]
|-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
16:18:23.687 [main] INFO chapter3.MyApp2 - Entering application.
16:18:23.687 [main] DEBUG chapter3.Foo - Did it again!
16:18:23.687 [main] INFO chapter3.MyApp2 - Exiting application.
At the end of this output, you can recognize the lines that were
printed in the previous example. You should also notive the
logback's internal messages based on Status objects,
logback internal reporting mechanism. Status objects
allow easy access to logback's internal state.
XML Syntax
To obtain these different logging behaviors we do not need to
recompile code. You can easily configure logging so that output is
directed to a UNIX Syslog daemon, or to a log visualizer, or forward
logging events to a remote logback server, which would log according
to local server policy, for example by forwarding the log event to a
second logback server.
The remainder of this chapter presents the syntax of the
directives allowing all these variations.
Configuring Loggers
Loggers are configured using logger elements. A logger element takes exactly
one mandatory name atttribute and an optional additivity attribute, which takes values
true or false. The logger element admits at most one level
element which is discussed next. It may also contain zero or more appender-ref elements;
each appender thus referenced is added to the named logger. It is important to keep mind that
each named logger that is declared with a logger element first has all its
appenders removed and only then are the referenced appenders attached to it.
In particular, if there are no appender references, then the named logger will
lose all its appenders.
The level element is used to set logger levels. It admits two attributes
value and class. The value attribute can be one of the strings DEBUG,
INFO, WARNERROR, ALL or OFF.
The special case-insensitive value INHERITED, or its synonym NULL,
will force the level of the logger to be inherited from higher up in the hierarchy.
Note that the level of the root logger cannot be inherited.
If you set the level of a logger and later decide that it should inherit
its level, then you need to specify INHERITED or its synonym NULL
as the level value. The class attribute allows you to specify a custom
level where the value of the attribute is the fully qualified name of a
custom level class. You may alternatively use the level#classname syntax within
the value attribute. The level element has no children.
The root element configures the root logger. It does not admit
any attributes because the additivity flag does not apply to the root logger.
Moreover, since the root logger cannot be named, it does not admit a name
attribute either. The root element admits at most one level
element and zero or more appender-ref elements.
Similar to the logger element, declaring a root element
will have the effect of first closing and then detaching all its current
appenders and only subsequently will referenced appenders, if any, will be added.
In particular, if it has no appender references, then the root logger
will lose all its appenders.
Setting the level of a logger is as simple as declaring it and setting
its level, as the next example illustrates. Suppose we are no longer interested
in seeing any DEBUG level logs from any component
belonging to the chapter3 package. The following configuration file shows how to achieve that.
Example 3.5: Setting the level of a logger (logback-examples/src/main/java/chapter3/sample2.xml)
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="chapter3">
<level value="INFO" />
</logger>
<root>
<!-- The following level element is not necessary since the -->
<!-- level of the root level is set to DEBUG by default. -->
<level value="DEBUG" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
This new configuration will yield the following output, when used with the
same chapter3.MyApp2 class.
17:34:07.578 [main] INFO chapter3.MyApp2 - Entering application.
17:34:07.578 [main] INFO chapter3.MyApp2 - Exiting application.
Obviously, you can configure the levels of as many loggers as you wish.
In the next configuration file we set the level of the chapter3 logger to
INFO but at the same time set the level of the chapter3.Foo logger
to DEBUG.
Example 3.6: Setting the level of multiple loggers (logback-examples/src/main/java/chapter3/sample3.xml)
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<logger name="chapter3">
<level value="INFO" />
</logger>
<logger name="chapter3.Foo">
<level value="DEBUG" />
</logger>
<root>
<!-- The following level element is not necessary since the -->
<!-- level of the root level is set to DEBUG by default. -->
<level value="DEBUG" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
Running MyApp2 with this configuration file will result in the
following output on the console:
17:39:27.593 [main] INFO chapter3.MyApp2 - Entering application.
17:39:27.593 [main] DEBUG chapter3.Foo - Did it again!
17:39:27.593 [main] INFO chapter3.MyApp2 - Exiting application.
After JoranConfigurator configures logback using the sample3.xml
file, the logger settings, more specifically their levels, are summarized in the following table.
Logger name
Assigned Level
Effective Level
root
DEBUG
DEBUG
chapter3
INFO
INFO
chapter3.MyApp2
null
INFO
chapter3.Foo
DEBUG
DEBUG
It follows that the two logging statements of level INFO in the MyApp2
class are enabled while the debug statement in Foo.doIt() method
will also print without hindrance. Note that the level of the root logger is always
set to a non-null value, which is DEBUG by default.
One rather important point to remember is that the logger-level filter depends
on the effective level of the logger being invoked, not the level of the logger
where the appenders are attached. The configuration file sample4.xml is a case in point:
Example 3.7: Logger level sample (logback-examples/src/main/java/chapter3/sample4.xml)
The following table lists the loggers and their level setting after applying the
sample4.xml configuration file.
Logger name
Assigned Level
Effective Level
root
OFF
OFF
chapter3
INFO
INFO
chapter3.MyApp2
null
INFO
chapter3.Foo
null
INFO
The ConsoleAppender named STDOUT, the only configured appender in
sample4.xml, is attached to the root logger whose level is set to
OFF. However, running MyApp2 with configuration script
sample4.xml will output:
17:52:23.609 [main] INFO chapter3.MyApp2 - Entering application.
17:52:23.609 [main] INFO chapter3.MyApp2 - Exiting application.
Thus, the level of the root logger has no apparent effect because the loggers in
chapter3.MyApp2 and chapter3.Foo classes, namely
chapter3.MyApp2 and chapter3.Foo, inherit their level from the
chapter3 logger which has its level set to INFO.
As noted previously, the chapter3 logger exists by virtue of its
declaration in the configuration file - even if the Java source code does not
directly refer to it.
Configuring Appenders
Appenders are configured using appender elements. These elements admit
two attributes name and class both of which are mandatory.
The name attribute specifies the name of the appender whereas
the class attribute specifies the fully qualified name of the class
of which the named appender will be an instance.
The appender may contain zero or one layout elements and
zero or more filter elements. Appart from these two basic elements,
appender elements may contain any element that corresponds to a setter
method of the appender class, to configure the appender's options.
The layout element takes a mandatory class attribute specifying
the fully qualified name of the class of which the associated layout
should be an instance. Like the appender element, it may contain
other elements, referring to setter methods, to configure its options.
Logging to multiple appenders is as easy as defining the various appenders
and referencing them in a logger, as the next configuration file illustrates:
Example 3.8: Multiple loggers (logback-examples/src/main/java/chapter3/multiple.xml)
This configuration scripts defines two appenders called FILE and STDOUT.
The FILE appender logs to a file called myApp.log. The layout for this appender
is a PatternLayout that outputs the date, level, thread name, logger name,
file name and line number where the log request is located,
the message and line separator character(s).
The second appender called STDOUT outputs to the console.
The layout for this appender outputs only the message string followed by a line separator.
The appenders are attached to the root logger by referencing
them by name within an appender-ref element. Note that each appender
has its own layout. Layouts are usually not designed to be shared by multiple
appenders. XML configuration files do not provide any syntactical
means for sharing layouts.
By default, appenders are cumulative: a logger will log to the appenders
attached to itself (if any) as well as all the appenders attached to its ancestors.
Thus, attaching the same appender to multiple loggers will cause
logging output to be duplicated.
Example 3.9: Duplicate appender (logback-examples/src/main/java/chapter3/duplicate.xml)
Running MyApp2 with duplicate.xml will yield the following output:
14:25:36.343 [main] INFO chapter3.MyApp2 - Entering application.
14:25:36.343 [main] INFO chapter3.MyApp2 - Entering application.
14:25:36.359 [main] DEBUG chapter3.Foo - Did it again!
14:25:36.359 [main] DEBUG chapter3.Foo - Did it again!
14:25:36.359 [main] INFO chapter3.MyApp2 - Exiting application.
14:25:36.359 [main] INFO chapter3.MyApp2 - Exiting application.
Notice the duplicated output. The appender named STDOUT is attached to
two loggers, to root and to chapter3. Since the root logger is the
ancestor of all loggers and chapter3 is the parent of chapter3.MyApp2
and chapter3.Foo, logging request made with these two loggers
will be output twice, once because STDOUT is attached to chapter3
and once because it is attached to root.
Appender additivity is not intended as a trap for new users.
It is a quite convenient logback feature. For instance, you can configure
logging such that log messages appear on the console (for all loggers in the system)
while messages only from some specific set of loggers flow into a specific appender.
Example 3.10: Multiple appender (logback-examples/src/main/java/chapter3/restricted.xml)
In this example, the console appender will log all the messages (for all loggers in the system)
whereas only logs under the chapter3 tree go into the myApp.log file.
Overriding the default cumulative behaviour
In case the default cumulative behavior turns out to be unsuitable for
one's needs, one can override it by setting the additivity flag to false.
Thus, a branch in your logger tree may direct output to a set of appenders
different than those of the rest of the tree.
Example 3.11: Additivity flag (logback-examples/src/main/java/chapter3/additivityFlag.xml)
This example, the appender named FILE is attached to the chapter3.Foo
logger. Moreover, the chapter3.Foo logger has its additivity flag set to false
such that its logging output will be sent to the appender named FILE but
not to any appender attached higher in the hierarchy. Other loggers remain
oblivious to the additivity setting of the chapter3.Foo logger.
Running the MyApp2 application with the additivityFlag.xml
configuration file will output results on the console from the chapter3.MyApp2
logger.
However, output from the chapter3.Foo logger will appear in the foo.log file
and only in that file.
Variable substitution
All option values admit variable substitution. The syntax of variable
substitution is similar to that of Unix shells. The string between an
opening ${ and closing } is interpreted as a key.
The value of the substituted variable can be defined as a system property
in the configuration file itself or in a separate file linked to the
configuration file. The value of the key
is first searched in configuration file or linked properties file,
and if not found there, it is then searched in system properties.
The corresponding value replaces ${aKey} sequence. For example,
if java.home.dir system property is set to /home/xyz,
then every occurrence of the sequence ${java.home.dir} will be
interpreted as /home/xyz.
The first example shows a declared property at the beginning of the
configuration file. It is then used further down the file to specify
the place to create the output file.
Example 3.12: Simple Variable substitution (logback-examples/src/main/java/chapter3/variableSubstitution1.xml)
The next example shows the use of a System property to achieve the same result. The
property is not declared anywhere, thus logback will look for it in the System properties.
Example 3.13: System Variable substitution (logback-examples/src/main/java/chapter3/variableSubstitution2.xml)
When many variables are used, it is often more convenient to create
a separate file that will contain all the variables. Here is how one can
do such a setup.
Example 3.14: Variable substitution using a separate file (logback-examples/src/main/java/chapter3/variableSubstitution3.xml)
This configuration file contains a link to another file called variables1.properties.
The variables contained in that other file will be read and will thus be available to the
logback configuration file. Here is what the variable.properties file looks like.
Example 3.15: Variable file (logback-examples/src/main/java/chapter3/variables1.properties)
user.home.dir=/Users/seb
Nothing more is needed to declare the variable.
Recursive subsitution is also available. If the user wants to use variables to
specify not only the destination directory but also the file name, here is what she
would write in her variables1.properties file.
Example 3.16: Recursive use of variables (logback-examples/src/main/java/chapter3/variables2.properties)
It is possible to include configuration elements into a logback.xml
configuration file. This is done by using a include element, as shown
in the example below:
Example 3.18: File include (logback-examples/src/main/java/chapter3/redirectConfig.xml)
The file to be included can be referenced as a URL or as a
resource. To reference a URL use the url attribute
instead of the file attribute in the previous example. To reference a
resource, use the resource attribute instead of the file
attribute in the previous example.
Using Joran in your own application
As we've seen, logback relies on Joran, a
mature, flexible and powerful configuration framework. Many of the
capabilities offered by logback modules are possible with the help of Joran.
Joran is actually a generic configuration system which can be used
independently of logging. To emphaises this point, we should mention
that the logback-core module does not have a notion of loggers. In
that spirit, many of the examples related to this tutorial, have
nothing to do with loggers, appenders or layouts.
The examples for this chapter can be found under
LOGBACK_HOME/logback-examples/src/main/java/chapter3.
To install joran, simply download
logback and add logback-core-VERSION.jar to your classpath.
Historical perspective
One of the most powerful features of the Java language is
reflection. Reflection makes it possible to configure software systems
declaratively. For example, many important properties of an EJB are
configured with the ejb.xml file. While EJBs are written in Java, many
of their properties are specified within the ejb.xml file. Similarly,
logback settings can be specified in a configuration file, expressed
in XML format.
In log4j, logback's predecessor, DOMConfigurator that
shipped with log4j version 1.2.x can parse configuration files written
in XML. The DOMConfigurator was written in a way that
forced to tweak it each time the structure of the configuration file
changed. The modified code had to be recompiled and redeployed. Just
as importantly, the code of the DOMConfigurator consists of loops
dealing with children elements containing many interspersed if/else
statements. One could not help but notice that that particular code
reeked of redundancy. The digester
project has shown that it is possible to parse XML files using
pattern matching rules. At parse time, digester will apply the rules
that match previously stated patterns. Rule classes are usually quite
small and specialized. Consequently, they are relatively easy to
understand and to maintain.
Joran is heavily inspired by the commons-digester project but uses
a slightly different terminology. In commons-digester, a rule can be
seen as consisting of a pattern and a rule, as shown by the
Digester.addRule(String pattern, Rule rule) method. We
find it unnecessarily confusing to have a rule to consist of itself,
not recursively but with a different meaning. In Joran, a rule
consists of a pattern and an action. An action is invoked when a match
occurs for the corresponding pattern. This relation between patterns
and actions lies at the core of Joran. Quite remarkably, one can deal
with quite complex requirements by using simple patterns, or more
precisely with exact matches and wildcard matches. For example, the
pattern a/b will match a <b> element nested within
an <a> element but not a <c> element,
even if nested within a <b> element. It is also
possible to match a particular XML element, regardless of its nesting
level, by using the * wildcard character. For example, the pattern
*/a will match an <a> element at any nesting
position within the document. Other types of patterns, for example
a/*, are not currently supported by Joran.
SAX or DOM?
Due to the event-based architecture of the SAX API, a tool based on
SAX cannot easily deal with forward references, that is, references to
elements which are defined later than the current element being
processed. Elements with cyclical references are equally
problematic. More generally, the DOM API allows the user to perform
searches on all the elements and make forward jumps.
This extra flexibility initially led us to choose the DOM API as
the underlying parsing API for Joran. After some experimentation, it
quickly became clear that dealing with jumps to distant elements while
parsing the DOM tree did not make sense when the interpretation rules
were expressed in the form of patterns and actions. Joran only
needs to be given the elements in the XML document in a sequential,
depth-first order.
Joran was first implemented in DOM. However, the author migrated to
SAX in order to benefit form the location information provided to the
user, that is, to an org.w3.sax.ContentHandler. With the
help of location information, it becomes possible to display essential
error reports to the user which include exact line and column. This
extra information turns out to be handy in hunting down problems.
Actions
Actions extend the
ch.qos.logback.core.joran.action.Action class which
consists of the following abstract methods.
package ch.qos.logback.core.joran.action;
import org.xml.sax.Attributes;
import ch.qos.logback.core.joran.spi.ExecutionContext;
public abstract class Action {
/**
* Called when the parser first encounters an element.
*/
public abstract void begin(ExecutionContext ec,
String name,
Attributes attributes);
/**
* Called when the parser encounters the element end. At
* this stage, we can assume that child elements, if any,
* have been processed.
*/
public abstract void end(ExecutionContext ec, String name);
}
Thus, every action must implement the begin and end methods.
Execution context
To allow various actions to collaborate, the invocation of begin
and end methods include an execution context as the first
parameter. The execution context includes an object stack, an object
map, an error list and a reference to the Joran interpreter invoking
the action. Please see the
ch.qos.logback.core.joran.spi.ExecutionContext class for
the exact list of fields contained in the execution context.
Actions can collaborate together by fetching, pushing or popping
objects from the common object stack, or by putting and fetching keyed
objects on the common object map. Actions can report any error
conditions by adding error items on the execution context's
StatusManager.
A hello world example
The logback-examples/src/main/java/chapter3/helloWorld/ directory includes a
trivial action and Joran interpreter setup which just displays Hello
World when a <hello-world> element is encountered in an XML file.
It also includes the basic steps which are
necessary to set up and invoke a Joran interpreter.
The hello.xml file contains only one element, without any
other nested elements. The HelloWorldAction class is
a trivial implementation: it only prints "Hello World" in the console when
it's begin() method is called.
HelloWorld
is a class that sets up the Joran interpreter,
with the minimal steps necessary:
It creates a RuleStore and a Context
It adds the hello-world pattern, with it's corresponding action
It creates a Joran interpreter, and passes the RuleStore
It creates a SAX parser and parses the given file, specifying the newly created
Joran interpreter as the ContentHandler
It's last step is to print the content of the Context.
Since Joran uses logback's powerfull Status objects for
error reporting, one can have a good feedback on what happened during
the parsing.
In this example, the parsing is rather simple. The hello-world element
will activate HelloWorldAction's begin() and
end() methods.
In the first method, a simple call to System.out.println()
will be issued, displaying Hello World in the console.
Collaborating actions
The logback-examples/src/main/java/joran/calculator/ directory includes several actions
which collaborate together through the common object stack in order
to accomplish simple computations.
The calculator1.xml file contains a computation element,
with a nested literal element.
In the Calculator1 class, we declare various patterns and actions,
that will collaborate and calculate a result based on the xml file. The simple
calculator1.xml file only creates a computation and declares a literal
value. The resulting parsing is pretty simple:
What is interesting here is the way that the Actions collaborate.
The LiteralAction reads a literal value and pushes it in the
object stack maintained by the ExecutionContext. Once done,
any other action can pop the value to read or modify it. Here, the
end() method of the ComputationAction1 class pops
the value from the stack and prints it.
The calculator2.xml file is a bit more complex, but much more interesting.
It contains the following elements:
Example 3.19: Calculator configuration file (logback-examples/src/main/java/chapter3/calculator/calculator2.xml)
Here, there are obviously more actions that will be part of the computation.
When called, the AddAction class will remove the two integers at
the bottom of the stack, add them and push the resulting integer at the
top of the stack, for further use.
Later in the computation, the MultiplyAction class will be called.
It will take the last two integers from the stack, multiply them and
push the result in the stack.
We have here two examples of action whose begin() method behaves in
a certain, predictable way, but whose end() methods are empty.
Finally, a calculator3.xml is also provided, to demonstrate the possibility
elements that contain instances of the same element. Here's the content of
calculator3.xml:
Example 3.20: Calculator configuration file (logback-examples/src/main/java/chapter3/calculator/calculator3.xml)
Much like the use of parentheses in an algebrical equation, the presence of
a computation element nested in another is managed by the
ComputationAction2 class using an internal stack. The well-formedness
of XML will guarantee that a value saved by one begin() will be consumed
only by the matching end() method.
New-rule action
Joran includes an action which allows the Joran interpreter to lean
new rules on the fly while interpreting the XML file containing the
new rules. See the logback-examples/src/main/java/joran/newRule/
directory for sample code.
In this package, the NewRuleCalculator class contains
the same setup as we have seen so far, but for one line:
ruleStore.addRule(new Pattern("/computation/new-rule"), new NewRuleAction());
By adding this line, we ask Joran to allow new rules to be learnt
at parsing time. It works pretty much like the other rules: it has a
begin() and end() method, and is called each time
the parser finds a new-rule element.
When called, the begin() method looks for a pattern
and a actionClass attribute. The action class is then instanciated
and added to the RuleStore, along with its corresponding pattern.
Here is how new rules can be declared in an xml file:
The rules defined thus far are called explicit rules because they
require an explicit pattern, hence fixing the tag name of the elements
for which they apply.
In highly extensible systems, the number and type of components to
handle are innumerable so that it would become very tedious or even
impossible to list all the applicable patterns by name.
At the same time, even in highly extensible systems one can observe
well-defined patterns linking the various parts together. Implicit
rules come in very handy when processing components composed of
sub-components unknown ahead of time. For example, Apache Ant is
capable of handling tasks which contain tags unknown at compile time
by looking at methods whose names start with add, as in
addFile, or addClassPath.
When Ant encounters an embedded tag within a task, it
simply instantiates an object that matches the signature of the task
class' add method and attaches the resulting object to the parent.
Joran includes similar capability in the form of implicit
actions. Joran keeps a list of implicit actions which can be applied
if no explicit pattern matches the current XML element. However,
applying an implicit action may not be always appropriate. Before
executing the implicit action, Joran asks an implicit action whether
it is appropriate in the current context. Only if the action replies
affirmatively does Joran interpreter invoke the (implicit)
action. This extra step makes it possible to support multiple implicit
actions or obviously none, if no implicit action is appropriate for a
given situation.
For example, the NestedComponentIA extending
ImplicitAction , will
instantiate the class specified in a nested component and attach it
to the parent component by using setter method of the parent
component and the nested element's name. Under certain circumstances,
a nested action needs to be applied to an element say <a> and also
to another element <b> nested within <a>. The current
implementation of NestedComponentIA is capable of
handling multiply nested elements requiring intervention by the same
implicit action.
Both ImplicitAction and NestedComponentIA are located in the
ch.qos.logback.core.joran.action package.
Refer to the logback-examples/src/main/java/joran/implicit
directory for an example of an implicit action.
In that directory, you will find two actions classes, one xml file and one
class containing the setup of Joran.
The NOPAction class does nothing. It is used to set
the context of the foo element, using this line:
ruleStore.addRule(new Pattern("*/foo"), new NOPAction());
After that, the implicit action, namely
PrintMeImplicitAction,
is added to the RuleStore. This is done by simply adding a new
instance of the action to the Joran interpreter
When called, the isApplicable() method of PrintMeImplicitAction
checks the value of the printme attribute. If the value is true,
the implicit action is applicable: its begin() method will be called.
The implicit1.xml file contains the following lines:
Example 3.22: Usage of implicit rules (logback-examples/src/main/java/chapter3/implicit/implicit1.xml)
As one can see, the first element will be printed, since it has a printme
attribute, which bears the value true.
The second element will not be printed, because no printme attibute is present.
The last element will not be printed, although the required attribute is present.
This is because implicit rules are called only if no explicit rules are defined. Since
we added a NOPAction with the */foo pattern, it will be used instead
of the PrintMeImplicitAction.
Running the example yields the following output:
Element <xyz> asked to be printed.
Element <abc> asked to be printed.
ERROR in ch.qos.logback.core.joran.spi.ExecutionContext@1c5c1 - no applicable action \
for <xyz>, current pattern is [/foo/xyz]
The last line was printed because of a call to StatusPrinter at the end
of the main class.
Non goals
The Joran API is not intended to be used to parse documents with
thousands of elements.