Markers

Markers allow to tag log statements with a Marker object, labeling them as belonging to a specific type. For example, developers can use markers to tag log statements related to a particular subsystem or functionality.

By using markers, it is possible to filter log statements based on the Marker and display only those log statements that are of interest, such as those related to XML processing or SQL queries.

Markers offer more fine-grained control over log filtering beyond log levels or package names.

Creating Markers

Simple markers

To create a Marker, create a field in your class using the MarkerManager.getMarker() method:

private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");

Since a Marker is reusable across multiple log statements, storing it in a static final field makes it a constant. Once created, use it as the first argument in the log statement:

LOGGER.debug(SQL_MARKER, "SELECT * FROM {}", table);

If you use the configuration example below, one can see the following log statement on your console:

10:42:30.982 (SQL) SELECT * FROM my_table

Parent and child markers

A marker can have zero or more parent markers, allowing for a hierarchy of markers. To create such a hierarchy, you must use the addParents() method on the Marker object after you make the child marker.

private static final Marker QUERY_MARKER =
        MarkerManager.getMarker("SQL_QUERY").addParents(SQL_MARKER);
private static final Marker UPDATE_MARKER =
        MarkerManager.getMarker("UPDATE").addParents(SQL_MARKER);

Child markers do not differ from simple markers; one must pass them on as the first argument of a logging call.

LOGGER.debug(QUERY_MARKER, "SELECT * FROM {}", table);
LOGGER.debug(UPDATE_MARKER, "UPDATE {} SET {} = {}", table, column, value);

Messages marked with children’s markers behave as if they were both marked with the children’s marker and all its parents. If you use the configuration example below, you’ll see the following log statement on your console:

10:42:30.982 (SQL_QUERY[ SQL ]) SELECT * FROM my_table
10:42:30.982 (SQL_UPDATE[ SQL ]) UPDATE my_table SET column = value

Pitfalls

It is important to note that marker names must be unique, as Log4j registers them permanently by name. Developers are advised to avoid generic marker names, as they may conflict with those provided by third parties.

For technical reasons the Marker.setParents(Marker…​) method can be called at runtime to modify the list of parents of the current marker. However, we discourage such a practice and advise you to only use the method at initialization time.

It is also worth noting that markers without parents are more efficient to evaluate than markers with multiple parents. It is generally a good idea to avoid complex hierarchies of markers where possible.

Configuring filtering

Developers can use markers to filter the log statements delivered to log files. Marker processing is supported at least by Logback and the Log4j Core logging implementations. We will provide a sample configuration for both these backends.

Log4j Core

To filter messages by marker, you need to add MarkerFilter to your configuration file. For example, you can use the configuration below to redirect all SQL-related logs to the SQL_LOG appender, regardless of the level of the events:

  • XML

  • JSON

  • YAML

  • Properties

Snippet from an example log4j2.xml
<Appenders>
  <Console name="SQL_LOG">
    <PatternLayout pattern="%d{HH:mm:ss.SSS} (%marker) %m%n"/>
  </Console>
</Appenders>
<MarkerFilter marker="SQL"
              onMatch="ACCEPT"
              onMismatch="NEUTRAL"/>(1)
<Loggers>
  <Root level="INFO">
    <AppenderRef ref="SQL_LOG">
      <MarkerFilter marker="SQL"/>(2)
    </AppenderRef>
  </Root>
</Loggers>
Snippet from an example log4j2.json
"Appenders": {
  "Console": {
    "name": "SQL_LOG",
    "PatternLayout": {
      "pattern": "%d{HH:mm:ss.SSS} (%marker) %m%n"
    }
  }
},
"MarkerFilter": { (1)
  "marker": "SQL",
  "onMatch": "ACCEPT",
  "onMismatch": "NEUTRAL"
},
"Loggers": {
  "Root": {
    "level": "INFO",
    "AppenderRef": {
      "ref": "SQL_LOG",
      "MarkerFilter": { (2)
        "marker": "SQL"
      }
    }
  }
}
Snippet from an example log4j2.yaml
Appenders:
  Console:
    name: "SQL_LOG"
    PatternLayout:
      pattern: "%d{HH:mm:ss.SSS} (%marker) %m%n"
MarkerFilter: (1)
  marker: "SQL"
  onMatch: "ACCEPT"
  onMismatch: "NEUTRAL"
Loggers:
  Root:
    level: "INFO"
    AppenderRef:
      ref: "SQL_LOG"
      MarkerFilter: (2)
        marker: "SQL"
Snippet from an example log4j2.properties
appender.0.type = Console
appender.0.name = SQL_LOG
appender.0.layout.type = PatternLayout
appender.0.layout.pattern = %d{HH:mm:ss.SSS} (%marker) %m%n

(1)
filter.0.type = MarkerFilter
filter.0.marker = SQL
filter.0.onMatch = ACCEPT
filter.0.onMismatch = NEUTRAL

rootLogger.level = INFO
rootLogger.appenderRef.0.ref = SQL_LOG
(2)
rootLogger.appenderRef.0.filter.type = MarkerFilter
rootLogger.appenderRef.0.filter.marker = SQL
1 Accepts all events marked with SQL regardless of their level,
2 Only allow events marked with SQL or one of its children to be sent to the SQL_LOG appender.

Logback

Logback differentiates two kinds of filters: TurboFilters, which are applied before a log event is created, and Filters, which are applied only when a log event reaches an appender. See Logback filters for more information.

You can use a combination of MarkerFilter, EvaluatorFilter and OnMarkerEvaluator to redirect all messages marked with SQL to a specific appender, regardless of their level. In order to do that, you can use a configuration as below:

Snippet from an example logback.xml
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter"> (1)
  <Marker>SQL</Marker>
  <OnMatch>ACCEPT</OnMatch>
</turboFilter>
<appender name="SQL_LOG" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%d{HH:mm:ss.SSS} (%marker) %m%n</pattern>
  </encoder>
  <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> (2)
    <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
      <marker>SQL</marker>
    </evaluator>
    <onMismatch>DENY</onMismatch>
  </filter>
</appender>
<root level="INFO">
  <appender-ref ref="SQL_LOG"/>
</root>
1 Accepts all events marked with SQL regardless of their level,
2 Only allow events marked with SQL or one of its children to be sent to the SQL_LOG appender.git

Complete example

To try the examples on this page:

  • add MarkerExample.java to the src/main/java/example folder of your project,

  • if your project uses Log4j Core add log4j2.xml to the src/main/resources folder of your project.

  • if your project uses Logback add logback.xml to the src/main/resources folder of your project.