Flow Tracing
Log4j API provides convenience logging methods to aid the tracking of the data flow through an application, which is referred to as flow tracing. Logging implementations can choose to generate specialized log events allowing users to handle these messages different from the rest – see Example configuration.
Flow tracing is known to help with the following use cases:
-
Troubleshooting without requiring a debugging session
-
Helping educate new developers in learning the application
Usage
Logger
provides following methods for flow tracing purposes:
traceEntry()
-
Marks the entry to a block
traceExit()
-
Marks the exit from a block
catching()
-
Reports caught exceptions
throwing()
-
Reports exceptions that are either discarded or unlikely to be handled
The most used traceEntry()
and traceExit()
methods are intended to mark the entry and exit points of a particular block of code:
traceEntry()
and traceExit()
usagepublic void someMethod() {
LOGGER.traceEntry(); (1)
// method body
LOGGER.traceExit(); (2)
}
1 | traceEntry() marks the entry to the block |
2 | traceExit() marks the exit from the block |
Both traceEntry()
and traceExit()
also accept parameters.
You can use them to track the input and output of the associated block:
traceEntry()
and traceExit()
usage with argumentspublic String someMethod(String input) {
logger.traceEntry(null, input); (1)
// method body
String output = ...;
return logger.traceExit(output); (2)
}
1 | traceEntry() marks the entry to the block along with the input |
2 | traceExit() marks the exit from the block along with the output |
The catching()
method can be used by an application when it catches an exception that it will not rethrow, either explicitly or attached to another exception:
catching()
usagepublic void someMethod() {
try {
// Business logic
} catch (Exception error) {
logger.catching(error); (1)
}
}
1 | catching() reports the caught exception |
The throwing()
method can be used by an application when it is throwing an exception that is unlikely to be handled, such as a RuntimeException
.
This will ensure that proper diagnostics are available if needed.
throwing()
usagepublic void someMethod() {
try {
// Business logic
} catch (RuntimeException error) {
throw logger.throwing(error); (1)
}
}
1 | thrown() reports the caught exception that is about to be rethrown.
Plus, rethrowing the exception, contrary to the catching() example suppressing the exception. |
public void someMethod() {
try {
// Let's assume an exception is thrown here
String msg = messages[messages.length];
} catch (Exception ex) {
logger.throwing(ex); (1)
}
}
1 | The throwing() method logs exceptions that are thrown and not caught. |
Aspect-oriented programming
Logging has been a notorious example for demonstrating aspect-oriented programming (AOP).
For instance, using AOP, you can inject logging statements to methods that match a particular footprint, e.g., all public methods in com.mycompany
package.
With a couple of lines of AOP instructions, you can log input and output of all matching functions.
Flow tracing methods fits like a glove to this AOP use case.
You can see a demonstration of this in the log4j-samples-aspectj
project demonstrating how you can implement this use case using Log4j API flow methods and Spring Boot AspectJ support.
Implementation
This section explains how flow tracing is implemented by different logging implementations.
Log4j Core
Log4j Core, the reference implementation of Log4j API, implements the flow tracing methods such that
Log events
Log4j Core implements the flow tracing methods such that the generated log events are decorated to accommodate any need to selectively handle them:
Method | Level | Markers | Message |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
Plain message |
|
|
|
Plain message |
Configuration
Flow tracing implementation of Log4j Core can be configured using the following system properties:
log4j2.flowMessageFactory
Env. variable |
|
---|---|
Type |
|
Default value |
Fully qualified class name of a
FlowMessageFactory
implementation to be used by all loggers.
Logback
Logback is another logging implementation for the SLF4J logging API, just like Log4j Core is a logging implementation for the Log4j API. Using Log4j-to-SLF4J bridge, Logback can be configured to consume Log4j API.
Log4j-to-SLF4J bridge implements flow tracing methods such that log events are generated with same marker and level changes of Log4j Core.
They only differ in generated message types, since SLF4J has no extensible message contract like Log4j, but plain String
-based messages.
This effectively means Logback also has access to the stated marker and level changes while using Log4j API flow tracing methods.
Example configuration
In this section, we will share logging implementation configuration examples to filter on flow tracing log events.
Log4j Core
Log4j Core is the reference implementation of Log4j API. In the example configurations below, we will employ a Pattern Layout depending on the associated markers of the log event:
-
XML
-
JSON
-
YAML
-
Properties
<MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>(1)
<Appenders>
<Console name="CONSOLE">
<PatternLayout>
<MarkerPatternSelector defaultPattern="%d %5p [%t] %c{1} -- %m%n">(2)
<PatternMatch key="ENTER" pattern="%d %5p [%t] %c{1} => %m%n"/>(3)
<PatternMatch key="EXIT" pattern="%d %5p [%t] %c{1} <= %m%n"/>(4)
</MarkerPatternSelector>
</PatternLayout>
</Console>
</Appenders>
"MarkerFilter": { (1)
"marker": "FLOW",
"onMatch": "ACCEPT",
"onMismatch": "NEUTRAL"
},
"Appenders": {
"Console": {
"name": "CONSOLE",
"PatternLayout": {
"MarkerPatternSelector": {
"defaultPattern": "%d %5p [%t] %c{1} -- %m%n",(2)
"PatternMatch": [
{(3)
"key": "ENTER",
"pattern": "%d %5p [%t] %c{1} => %m%n"
},
{(4)
"key": "EXIT",
"pattern": "%d %5p [%t] %c{1} <= %m%n"
}
]
}
}
}
},
MarkerFilter: (1)
marker: "FLOW"
onMatch: "ACCEPT"
onMismatch: "NEUTRAL"
Appenders:
Console:
name: "CONSOLE"
PatternLayout:
MarkerPatternSelector:
defaultPattern: "%d %5p [%t] %c{1} -- %m%n" (2)
PatternMatch:
- key: "ENTER" (3)
pattern: "%d %5p [%t] %c{1} => %m%n"
- key: "EXIT" (4)
pattern: "%d %5p [%t] %c{1} <= %m%n"
filter.0.type = MarkerFilter (1)
filter.0.marker = FLOW
filter.0.onMatch = ACCEPT
filter.0.onMismatch = NEUTRAL
appender.0.type = Console
appender.0.name = CONSOLE
appender.0.layout.type = PatternLayout
appender.0.layout.patternSelector.type = MarkerPatternSelector
appender.0.layout.patternSelector.defaultPattern = "%d %5p [%t] %c{1} -- %m%n (2)
appender.0.layout.patternSelector.properties.0.type = PatternMatch
appender.0.layout.patternSelector.properties.0.key = ENTER
appender.0.layout.patternSelector.properties.0.pattern = %d %5p [%t] %c{1} => %m%n (3)
appender.0.layout.patternSelector.properties.1.type = PatternMatch
appender.0.layout.patternSelector.properties.1.key = EXIT
appender.0.layout.patternSelector.properties.1.pattern = %d %5p [%t] %c{1} <= %m%n (4)
1 | Accepting log events marked with FLOW regardless of their level |
2 | By default, log event messages will be formatted with a -- prefix |
3 | Log events marked with ENTER (that is, generated by traceEntry() ) will have their message formatted with a ⇒ prefix |
4 | Log events marked with EXIT (that is, generated by traceExit() ) will have their message formatted with a ⇐ prefix |
Logback
Logback is another logging implementation for the SLF4J logging API, just like Log4j Core is a logging implementation for the Log4j API. Using Log4j-to-SLF4J bridge, Logback can be configured to consume Log4j API.
Below we will use a combination of EvaluatorFilter
, MarkerFilter
, and OnMarkerEvaluator
in Logback to adapt the formatting pattern of messages written to console based on flow tracing specific markers:
<turboFilter class="MarkerFilter">(1)
<Marker>FLOW</Marker>
<OnMatch>ACCEPT</OnMatch>
</turboFilter>
<appender name="CONSOLE_DEFAULT" class="ConsoleAppender">
<filter class="EvaluatorFilter">(2)
<evaluator class="OnMarkerEvaluator">
<marker>ENTER</marker>
<marker>EXIT</marker>
</evaluator>
<onMismatch>ACCEPT</onMismatch>
<onMatch>DENY</onMatch>
</filter>
<encoder class="PatternLayoutEncoder">(3)
<pattern><![CDATA[%d %5p [%t] %c{1} -- %m%n]]></pattern>
</encoder>
</appender>
<appender name="CONSOLE_FLOW_ENTER" class="ConsoleAppender">
<filter class="EvaluatorFilter">(4)
<evaluator class="OnMarkerEvaluator">
<marker>ENTER</marker>
</evaluator>
<onMismatch>DENY</onMismatch>
<onMatch>ACCEPT</onMatch>
</filter>
<encoder class="PatternLayoutEncoder">(5)
<pattern><![CDATA[%d %5p [%t] %c{1} => %m%n]]></pattern>
</encoder>
</appender>
<appender name="CONSOLE_FLOW_EXIT" class="ConsoleAppender">
<filter class="EvaluatorFilter">(6)
<evaluator class="OnMarkerEvaluator">
<marker>EXIT</marker>
</evaluator>
<onMismatch>DENY</onMismatch>
<onMatch>ACCEPT</onMatch>
</filter>
<encoder class="PatternLayoutEncoder">(7)
<pattern><![CDATA[%d %5p [%t] %c{1} <= %m%n]]></pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="CONSOLE_DEFAULT"/>
<appender-ref ref="CONSOLE_FLOW_ENTER"/>
<appender-ref ref="CONSOLE_FLOW_EXIT"/>
</root>
1 | Accepting log events marked with FLOW regardless of their level |
2 | In CONSOLE_DEFAULT appender, excluding all log events marked with ENTER and EXIT |
3 | In CONSOLE_DEFAULT appender, log event messages will be formatted with a - prefix |
4 | In CONSOLE_FLOW_ENTER appender, accepting only log events marked with ENTER |
5 | In CONSOLE_FLOW_ENTER appender, log event messages will be formatted with a → prefix |
6 | In CONSOLE_FLOW_EXIT appender, accepting only log events marked with EXIT |
7 | In CONSOLE_FLOW_EXIT appender, log event messages will be formatted with a ← prefix |