Tentackle Logging Log4J 2 — The Log4J 2 Provider¶
Overview and Motivation¶
tentackle-log-log4j2v is one of the pluggable logging backend providers for Tentackle. Tentackle code never logs
against a concrete logging library; it logs through the backend-agnostic facade in org.tentackle.log (part of
tentackle-core). This module binds that facade to Apache Log4J 2.
The artifact name reads "log4j2v" — Log4J version 2, deliberately distinct from the long-dead Log4J 1.x.
For the full picture of the facade — the
LoggerAPI, levels, MDC,@Log, method statistics and how a backend is discovered and bound — see the logging deep-dive. This page documents only the Log4J 2-specific provider.
Selecting Log4J 2 is purely a matter of dependencies: put this module on the path, and Tentackle routes all of its
logging through Log4J 2. With no provider module present, Tentackle falls back to plain java.util.logging.
<!-- route Tentackle logging through Log4J 2 -->
<dependency>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-log-log4j2v</artifactId>
</dependency>
The module depends on log4j-api and log4j-core (the implementation), so no further Log4J dependency is
needed. The dependency on tentackle-core is declared optional in the POM: the application declares
tentackle-core itself, and the provider deliberately doesn't drag it in.
Only log4j-api is requiresd in module-info.java, since the main code talks to the API exclusively.
log4j-core is a runtime-only dependency that Log4J locates via its own service lookup. (Both jars are modular;
the module's tests, which do access log4j-core, add the missing read edge via
--add-reads org.tentackle.log.log4j2v=org.apache.logging.log4j.core in the surefire argLine.)
How It Binds¶
The module ships a single Logger implementation,
Log4J2Logger, annotated @Service(Logger.class). The
@Service annotation processor generates META-INF/services/org.tentackle.log.Logger, so when this jar is on the
path DefaultLoggerFactory discovers it and binds the facade to Log4J 2 — no configuration required. (See
How a Backend Is Selected in the
core doc.)
Log4J2Logger provides a static getLogger(String) factory, which DefaultLoggerFactory prefers over the public
Log4J2Logger(String) constructor. It caches one instance per name in a synchronized map; the instances are never
evicted.
Level Mapping¶
The five Tentackle levels map onto Log4J's Level as follows:
| Tentackle | Log4J 2 |
|---|---|
FINER |
TRACE |
FINE |
DEBUG |
INFO |
INFO |
WARNING |
WARN |
SEVERE |
ERROR |
Level guards (isFineLoggable() etc.) all funnel through logger.isEnabled(translateLevel(level)). The logging
methods themselves are guarded the same way (see below), so disabled levels cost almost nothing and lazy Supplier
messages are never evaluated.
Location Awareness¶
A naïve facade reports its own class and line number as the origin of every log statement, which makes
pattern-layout %C/%L/%M output useless. Log4J2Logger avoids this by wrapping the Log4J logger in an
ExtendedLoggerWrapper
and logging through logIfEnabled(FQCN, …), passing its own classname as the fully-qualified class name to
exclude from the stack walk. Log4J then attributes each line to the caller rather than to the wrapper.
Consequently, getLoggerImpl() returns that ExtendedLoggerWrapper, not the org.apache.logging.log4j.Logger
obtained from the LogManager. Both share the same name and configuration.
Message Formatting¶
Log4J's native {} placeholder syntax is not used. Tentackle's facade uses java.text.MessageFormat
({0}, {1}, …) uniformly across all backends, so Log4J2Logger formats the message with MessageFormat itself
and hands Log4J a finished SimpleMessage. The message (and any parameter suppliers) are wrapped in a
Supplier<Message> passed to logIfEnabled, so they are resolved only when the level is actually enabled and
unused arguments are never computed.
Whether MessageFormat is applied at all depends on the overload:
| Call | Formatting |
|---|---|
info(String), info(String, Throwable) |
none — the message is logged verbatim |
info(String, Object...) with parameters |
MessageFormat |
info(String, Object...) without parameters |
none — the message is logged verbatim |
info(String, Supplier<?>...) |
always MessageFormat |
info(Supplier<String>) and variants |
none — the supplied string is logged verbatim |
So MessageFormat's quoting rules (a single quote must be doubled: '') apply only to formatted messages: a
verbatim "it''s" is logged with both quotes. A Log4J-style {} in a verbatim message is logged as is (a
SimpleMessage is never parameterized), but in a formatted message it is not a valid MessageFormat element:
info("value {}", x) throws an IllegalArgumentException in the caller's thread (if the level is enabled).
logStacktrace(…) prints the stacktrace through a
LoggerOutputStream, which
logs it line by line at the requested level (SEVERE by default).
Mapped Diagnostic Context¶
Log4J2MappedDiagnosticContext extends
AbstractMappedDiagnosticContext
and delegates put/get/remove/clear/getContext directly to Log4J's thread-local
org.apache.logging.log4j.ThreadContext. It is a singleton obtained via ServiceFactory (and can therefore be
replaced by an application @Service), and is what Log4J2Logger.getMappedDiagnosticContext() returns — so MDC
keys set through the Tentackle facade show up in Log4J's ThreadContext and can be referenced from a layout pattern
(%X). getContext() returns a copy of the current thread's context: modifying it does not change the MDC.
Packaging and Modularity¶
The module is fully modular:
module org.tentackle.log.log4j2v {
exports org.tentackle.log.log4j2v;
requires transitive org.tentackle.core;
requires org.apache.logging.log4j; // log4j-api only; log4j-core is a runtime-only dependency
provides org.tentackle.common.ModuleHook with org.tentackle.log.log4j2v.service.Hook;
}
The Hook ModuleHook provides resource-bundle access for
the module. For OSGi, the module is wrapped into the tentackle.log.log4j2v.osgi bundle with its own activator
(tentackle-osgi/tentackle-osgi-activators/tentackle-log-log4j2v-osgi-activator).
Log4J itself is configured the usual way (e.g., a log4j2.xml on the classpath); Tentackle adds nothing to that.
Source Map¶
| Type | Location |
|---|---|
Log4J2Logger (the @Service(Logger.class) provider) |
org.tentackle.log.log4j2v |
Log4J2MappedDiagnosticContext |
org.tentackle.log.log4j2v |
Hook (ModuleHook service) |
org.tentackle.log.log4j2v.service |
See Also¶
- Tentackle Logging — the pluggable logging API — the facade,
@Log, MDC, statistics and backend selection. - The sibling
tentackle-log-slf4jprovider — the same pattern bound to SLF4J.