Introduction
SLF4J is a very commonly used log library in many Java application. It is favored because it allows the end-user to plug in the desired logging framework at deployment time.
How is this feature implemented? How to use it? Let’s continue.
Binding
API & Implementation
SLF4J library has two main parts: api and implementations. Its implementations are referred as bindings.
In order to use SLF4J we have to both the api jar and bindings. We can replace slf4j bindings on our class path to switch logging frameworks.
If we start a project with only slf4j-api-x.jar
dependency, we will see the following warning:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
This is because if no binding is found on the class path, SLF4J will default to a no-operation implementation.
Change Binding
From the SLF4J doc:
SLF4J does not rely on any special class loader machinery. In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework.
And this can be verified from the LoggerFactory
source code:
StaticLoggerBinder.getSingleton().getLoggerFactory();
So, if we want to change the bindings, we can simply replace original implementation with another one. This is very useful if we want to use different logging system in test and in production, or our project will be used as a library.
What should be noticed is that, in addition to slf4j-api-x.jar
, we can simply drop one and only one binding of our choice onto the appropriate class path location. Placing more than one binding on class path will causing a warning emitted by SLF4J. But, even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and should be considered random.
Logger Hierarchy
Like other Java logging system, SLF4J has parent-child log hierarchy:
At the top of the hierarchy exists a root logger. The root logger exists outside the scope of any custom logger hierarchy that we may come up with. It always exists as the root logger for all possible logger hierarchies, and it has no namespace. All the other application-specific Logger objects are child objects to the root logger. The parent-child relationship of loggers signifies the dependency of the loggers acting within the same application.
And this can be seen from the source code logback.classic.Logger
:
/**
* The parent of this category. All categories have at least one ancestor
* which is the root category.
*/
transient private Logger parent;
/**
* The children of this logger. A logger may have zero or more children.
*/
transient private List<Logger> childrenList;
Down to Child Logger
When we have the parent-child hierarchy, what can we get from parent logger?
A child logger can inherit properties from its parent logger recursively up the tree. Typically, a child logger will inherit the following properties from its parent logger(s):
- Level: If the child logger has no explicit tree level specified, it will use the level of its closest parent or the first proper level it finds recursively up the hierarchy.
- Appender: If there is no appender attached to a logger, the child logger uses the appender of its closest parent logger or the first appender it finds recursively up the tree.
- ResourceBundle: ResourceBundles are key-value pattern properties files used for the localization of logging messages. A child logger inherits any ResourceBundle associated with its parent logger.
And also can be seen from source:
// The effective levelInt is the assigned levelInt and if null, a levelInt is
// inherited form a parent.
transient private int effectiveLevelInt;
Up to Father Logger
When we log, the event will go up to the father logger. But sometimes, we don’t want to share a private log, we can forbid the propagation of the log event by setting additive
.
Ref
Written with StackEdit.
评论
发表评论