Scripts

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

In order to enable a scripting language, its name must be included in the log4j2.scriptEnableLanguages 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

Snippet from an example log4j2.xml
<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>
Snippet from an example log4j2.json
"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"
  }
}
Snippet from an example log4j2.yaml
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"
Snippet from an example log4j2.properties
appender.0.type = Console
appender.0.name = STDOUT
appender.0.layout.type = PatternLayout

appender.0.layout.selector = ScriptPatternSelector
appender.0.layout.selector.defaultPattern = %d %p %m%n
appender.0.layout.selector.scriptRef.type = ScriptRef
appender.0.layout.selector.scriptRef.ref = SELECTOR_SCRIPT
appender.0.layout.selector.match[0].type = PatternMatch
appender.0.layout.selector.match[0].key = NoLocation
appender.0.layout.selector.match[0].pattern = [%-5level] %c{1.} %msg%n
appender.0.layout.selector.match[1].type = PatternMatch
appender.0.layout.selector.match[1].key = Flow
appender.0.layout.selector.match[1].pattern = \
  [%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n

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

rootLogger.level = INFO
rootLogger.filter.0.type = ScriptFilter
rootLogger.filter.0.onMatch = ACCEPT
rootLogger.filter.0.onMismatch = DENY
rootLogger.filter.0.scriptRef.type = ScriptRef
rootLogger.filter.0.scriptRef.ref = ROOT_FILTER
rootLogger.appenderRef.0.ref = STDOUT

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

script.1.type = ScriptFile
script.1.name = ROOT_FILTER
script.1.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 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)