Scripts

Log4j provides support for JSR 223 scripting languages to be used in some of its components.

Additional runtime dependencies are required to enable JSR 223 scripting:

  • Maven

  • Gradle

We assume you use log4j-bom for dependency management.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-script</artifactId>
  <scope>runtime</scope>
</dependency>

We assume you use log4j-bom for dependency management.

runtimeOnly 'org.apache.logging.log4j:log4j-script'

In order to enable a scripting language, its name must be included in the log4j.script.enableLanguages configuration property.

Each component that allows scripts can contain on of the following configuration elements:

Script

This element specifies the content of the script directly and has:

  • a required language configuration attribute that specifies the name of the JSR 223 language to use,

  • a required scriptText configuration attribute that contains the text of the script. In the XML configuration format, the text of the script can also be written as content of the <Script> XML element. This allows the usage of a CDATA block.

The element can be assigned a name using the name configuration attribute.

See also Plugin reference.

ScriptFile

This element points to an external script file and has:

  • a required path attribute that points to the path to a file name.

  • an optional language attribute that specifies the name of the JSR 223 language to use. If not provided, the language is deduced from the extension of the file.

  • an optional isWatched attribute. If set to true the script file will be monitored for changes.

The element can be assigned a name using the name configuration attribute.

See also Plugin reference.

ScriptRef

This element references a named script from the global Scripts container plugin in the configuration file.

See also Plugin reference.

The environment in which the script runs is different for each Log4j script-based component.

  • XML

  • JSON

  • YAML

  • Properties

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="https://logging.apache.org/xml/ns"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="
                   https://logging.apache.org/xml/ns
                   https://logging.apache.org/xml/ns/log4j-config-3.xsd">
  <Appenders>
    <Console name="STDOUT">
      <PatternLayout>
        <ScriptPatternSelector defaultPattern="%d %p %m%n">
          <ScriptRef ref="SELECTOR_SCRIPT"/>
          <PatternMatch key="NoLocation" pattern="[%-5level] %c{1.} %msg%n"/>
          <PatternMatch key="Flow"
                        pattern="[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"/>
        </ScriptPatternSelector>
      </PatternLayout>
    </Console>
  </Appenders>
  <Loggers>
    <Logger name="EventLogger">
      <ScriptFilter onMatch="ACCEPT" onMismatch="DENY">
        <Script name="EVENT_LOGGER_FILTER" language="groovy"><![CDATA[
          if (logEvent.getMarker() != null
              && logEvent.getMarker().isInstanceOf("FLOW")) {
            return true;
          } else if (logEvent.getContextMap().containsKey("UserId")) {
            return true;
          }
          return false;
          ]]>
        </Script>
      </ScriptFilter>
    </Logger>
    <Root level="INFO">
      <ScriptFilter onMatch="ACCEPT" onMismatch="DENY">
        <ScriptRef ref="ROOT_FILTER"/>
      </ScriptFilter>
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
  <Scripts>
    <Script name="SELECTOR_SCRIPT" language="javascript"><![CDATA[
      var result;
      if (logEvent.getLoggerName().equals("JavascriptNoLocation")) {
        result = "NoLocation";
      } else if (logEvent.getMarker() != null
          && logEvent.getMarker().isInstanceOf("FLOW")) {
        result = "Flow";
      }
      result;
      ]]>
    </Script>
    <ScriptFile name="ROOT_FILTER" path="scripts/filter.groovy"/>
  </Scripts>
</Configuration>
{
  "Configuration": {
    "Appenders": {
      "Console": {
        "name": "STDOUT",
        "PatternLayout": {
          "ScriptPatternSelector": {
            "defaultPattern": "%d %p %m%n",
            "ScriptRef": {
              "ref": "SELECTOR_SCRIPT",
              "PatternMatch": [
                {
                  "key": "NoLocation",
                  "pattern": "[%-5level] %c{1.} %msg%n"
                },
                {
                  "key": "Flow",
                  "pattern": "[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"
                }
              ]
            }
          }
        }
      },
      "Loggers": {
        "Logger": {
          "name": "EventLogger",
          "ScriptFilter": {
            "onMatch": "ACCEPT",
            "onMismatch": "DENY",
            "Script": {
              "name": "EVENT_LOGGER_FILTER",
              "language": "groovy",
              "scriptText": "if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf('FLOW'))) { return true; } else if (logEvent.getContextMap().containsKey('UserId')) { return true; } return false;"
            }
          }
        },
        "Root": {
          "level": "INFO",
          "ScriptFilter": {
            "onMatch": "ACCEPT",
            "onMismatch": "DENY",
            "ScriptRef": {
              "ref": "ROOT_FILTER"
            }
          },
          "AppenderRef": {
            "ref": "STDOUT"
          }
        },
        "Scripts": {
          "Script": {
            "name": "SELECTOR_SCRIPT",
            "language": "javascript",
            "scriptText": "var result; if (logEvent.getLoggerName().equals('JavascriptNoLocation')) { result = 'NoLocation'; } else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf('FLOW')) { result = 'Flow'; } result;"
          },
          "ScriptFile": {
            "name": "ROOT_FILTER",
            "path": "scripts/filter.groovy"
          }
        }
      }
    }
  }
}
Configuration:
  Appenders:
    Console:
      name: "STDOUT"
      PatternLayout:
        ScriptPatternSelector:
          defaultPattern: "%d %p %m%n"
          ScriptRef:
            ref: "SELECTOR_SCRIPT"
          PatternMatch:
            - key: "NoLocation"
              pattern: "[%-5level] %c{1.} %msg%n"
            - key: "Flow"
              pattern: "[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"
  Loggers:
    Logger:
      name: "EventLogger"
      ScriptFilter:
        onMatch: "ACCEPT"
        onMismatch: "DENY"
        Script:
          name: "EVENT_LOGGER_FILTER"
          language: "groovy"
          scriptText: |
            if (logEvent.getMarker() != null
                && logEvent.getMarker().isInstanceOf("FLOW")) {
              return true;
            } else if (logEvent.getContextMap().containsKey("UserId")) {
              return true;
            }
            return false;
    Root:
      level: "INFO"
      ScriptFilter:
        onMatch: "ACCEPT"
        onMismatch: "DENY"
        ScriptRef:
          ref: "ROOT_FILTER"
      AppenderRef:
        ref: "STDOUT"
  Scripts:
    Script:
      name: "SELECTOR_SCRIPT"
      language: "javascript"
      scriptText: |
        var result;
        if (logEvent.getLoggerName().equals("JavascriptNoLocation")) {
          result = "NoLocation";
        } else if (logEvent.getMarker() != null
            && logEvent.getMarker().isInstanceOf("FLOW")) {
          result = "Flow";
        }
        result;
    ScriptFile:
      name: "ROOT_FILTER"
      path: "scripts/filter.groovy"
Appenders.Console.name = STDOUT

Appenders.Console.PatternLayout.ScriptPatternSelector.defaultPattern = %d %p %m%n
Appenders.Console.PatternLayout.ScriptPatternSelector.ScriptRef.ref = SELECTOR_SCRIPT
Appenders.Console.PatternLayout.ScriptPatternSelector.PatternMatch[1].key = NoLocation
Appenders.Console.PatternLayout.ScriptPatternSelector.PatternMatch[1].pattern = [%-5level] %c{1.} %msg%n
Appenders.Console.PatternLayout.ScriptPatternSelector.PatternMatch[2].key = Flow
Appenders.Console.PatternLayout.ScriptPatternSelector.PatternMatch[2].pattern = \
  [%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n

Loggers.Logger.name = EventLogger
Loggers.Logger.ScriptFilter.onMatch = ACCEPT
Loggers.Logger.ScriptFilter.onMismatch = DENY
Loggers.Logger.ScriptFilter.Script.name = EVENT_LOGGER_FILTER
Loggers.Logger.ScriptFilter.Script.language = groovy
Loggers.Logger.ScriptFilter.Script.scriptText = \
  if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW"))) {\
    return true;\
  } else if (logEvent.getContextMap().containsKey("UserId")) {\
    return true;\
  }\
  return false;

Loggers.Root.level = INFO
Loggers.Root.ScriptFilter.onMatch = ACCEPT
Loggers.Root.ScriptFilter.onMismatch = DENY
Loggers.Root.ScriptFilter.ScriptRef.ref = ROOT_FILTER
Loggers.Root.AppenderRef.ref = STDOUT

Scripts.Script.name = SELECTOR_SCRIPT
Scripts.Script.language = javascript
Scripts.Script.scriptText = \
  var result;\
  if (logEvent.getLoggerName().equals("JavascriptNoLocation")) {\
    result = "NoLocation";\
  } else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {\
    result = "Flow";\
  }\
  result;

Scripts.ScriptFile.name = ROOT_FILTER
Scripts.ScriptFile.path = scripts/filter.groovy

A special note on Beanshell

JSR 223 scripting engines are supposed to identify that they support the Compilable interface if they support compiling their scripts.

Beanshell does extend the Compilable interface, but an attempt to compile a script ends up in an Error being thrown. Log4j catches the throwable, but issues a warning in the status logger.

2015-09-27 16:13:23,095 main DEBUG Script BeanShellSelector is compilable
2015-09-27 16:13:23,096 main WARN Error compiling script java.lang.Error: unimplemented
            at bsh.engine.BshScriptEngine.compile(BshScriptEngine.java:175)
            at bsh.engine.BshScriptEngine.compile(BshScriptEngine.java:154)
            at org.apache.logging.log4j.core.script.ScriptManager$MainScriptRunner.<init>(ScriptManager.java:125)
            at org.apache.logging.log4j.core.script.ScriptManager.addScript(ScriptManager.java:94)