You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2015/09/29 08:46:43 UTC

logging-log4j2 git commit: [LOG4J2-1136] Add support for JSR 223 scripts in filters and the PatternSelector: Add support for script files.

Repository: logging-log4j2
Updated Branches:
  refs/heads/LOG4J2-1136 dee44fffd -> b3596bf92


[LOG4J2-1136] Add support for JSR 223 scripts in filters and the
PatternSelector: Add support for script files.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/b3596bf9
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/b3596bf9
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/b3596bf9

Branch: refs/heads/LOG4J2-1136
Commit: b3596bf92b02f3b46b2b7c8a17e9997260909cf2
Parents: dee44ff
Author: ggregory <gg...@apache.org>
Authored: Mon Sep 28 23:46:40 2015 -0700
Committer: ggregory <gg...@apache.org>
Committed: Mon Sep 28 23:46:40 2015 -0700

----------------------------------------------------------------------
 .../logging/log4j/core/filter/ScriptFilter.java |  30 ++--
 .../log4j/core/script/AbstractScript.java       |  31 ++++
 .../logging/log4j/core/script/Script.java       |  40 ++---
 .../logging/log4j/core/script/ScriptFile.java   |  64 ++++++++
 .../log4j/core/script/ScriptManager.java        |  10 +-
 .../apache/logging/log4j/core/util/IOUtils.java | 114 +++++++++++++
 .../log4j/core/util/StringBuilderWriter.java    | 164 +++++++++++++++++++
 .../core/filter/AbstractScriptFilterTest.java   |  76 +++++++++
 .../log4j/core/filter/ScriptFileFilterTest.java |  37 +++++
 .../log4j/core/filter/ScriptFilterTest.java     |  53 +-----
 .../src/test/resources/log4j-script-filters.xml |  60 +++++++
 .../test/resources/log4j-scriptFile-filters.xml |  43 +++++
 .../src/test/resources/log4j-scriptFilters.xml  |  60 -------
 .../src/test/resources/scripts/filter.groovy    |   6 +
 log4j-core/src/test/resources/scripts/filter.js |   7 +
 15 files changed, 644 insertions(+), 151 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
index 2c65742..9e8acca 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.core.filter;
 
+import javax.script.SimpleBindings;
+
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.core.Filter;
@@ -28,25 +30,25 @@ import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.script.AbstractScript;
 import org.apache.logging.log4j.core.script.Script;
+import org.apache.logging.log4j.core.script.ScriptFile;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 
-import javax.script.SimpleBindings;
-
 /**
- * This filter returns the onMatch result if the script returns True and returns the onMisMatch value otherwise.
+ * Returns the onMatch result if the script returns True and returns the onMisMatch value otherwise.
  */
 @Plugin(name = "ScriptFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
 public final class ScriptFilter extends AbstractFilter {
 
     private static final long serialVersionUID = 1L;
 
-    private final Script script;
+    private final AbstractScript script;
     private final Configuration configuration;
 
-    private ScriptFilter(final Script script, final Configuration configuration, final Result onMatch,
+    private ScriptFilter(final AbstractScript script, final Configuration configuration, final Result onMatch,
                          final Result onMismatch) {
         super(onMatch, onMismatch);
         this.script = script;
@@ -118,8 +120,11 @@ public final class ScriptFilter extends AbstractFilter {
     }
 
     /**
-     * Create the ScriptFilter.
-     * @param script The script to run. The script must return a boolean value.
+     * Creates the ScriptFilter.
+     * @param script The script to run. The script must return a boolean value. Either script or scriptFile must be 
+     *      provided.
+     * @param scriptFile The script file to run. The script must return a boolean value. Either script or scriptFile 
+     *      must be provided.
      * @param match The action to take if a match occurs.
      * @param mismatch The action to take if no match occurs.
      * @param configuration the configuration 
@@ -128,15 +133,20 @@ public final class ScriptFilter extends AbstractFilter {
     @PluginFactory
     public static ScriptFilter createFilter(
             @PluginElement("Script") final Script script,
+            @PluginElement("ScriptFile") final ScriptFile scriptFile,
             @PluginAttribute("onMatch") final Result match,
             @PluginAttribute("onMismatch") final Result mismatch,
             @PluginConfiguration final Configuration configuration) {
 
-        if (script == null) {
-            LOGGER.error("A script must be provided for ScriptFilter");
+        if (script == null && scriptFile == null) {
+            LOGGER.error("A Script or ScriptFile element must be provided for this ScriptFilter");
+            return null;
+        }
+        if (script != null && scriptFile != null) {
+            LOGGER.error("One of a Script or ScriptFile element must be provided for this ScriptFilter, but not both");
             return null;
         }
-        return new ScriptFilter(script, configuration, match, mismatch);
+        return new ScriptFilter(script != null ? script : scriptFile, configuration, match, mismatch);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/script/AbstractScript.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/AbstractScript.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/AbstractScript.java
new file mode 100644
index 0000000..6a74653
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/AbstractScript.java
@@ -0,0 +1,31 @@
+package org.apache.logging.log4j.core.script;
+
+/**
+ * Container for the language and body of a script.
+ */
+public abstract class AbstractScript {
+
+    protected static final String DEFAULT_LANGUAGE = "JavaScript";
+    private final String language;
+    private final String scriptText;
+    private final String name;
+
+    public AbstractScript(final String name, final String language, final String scriptText) {
+        this.language = language;
+        this.scriptText = scriptText;
+        this.name = name == null ? this.toString() : name;
+    }
+
+    public String getLanguage() {
+        return this.language;
+    }
+
+    public String getScriptText() {
+        return this.scriptText;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
index 03e0c9e..539ee9e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
@@ -12,43 +12,27 @@ import org.apache.logging.log4j.status.StatusLogger;
  * Container for the language and body of a script.
  */
 @Plugin(name = "Script", category = Node.CATEGORY, printObject = true)
-public class Script {
+public class Script extends AbstractScript {
 
     private static final Logger logger = StatusLogger.getLogger();
 
-    private final String language;
-    private final String scriptText;
-    private final String name;
-
-    public Script(final String name, final String language, final String body) {
-        this.language = language;
-        this.scriptText = body;
-        this.name = name == null ? this.toString() : name;
-    }
-
-    public String getLanguage() {
-        return this.language;
-    }
-
-    public String getScriptText() {
-        return this.scriptText;
-    }
-
-    public String getName() {
-        return this.name;
+    public Script(String name, String language, String scriptText) {
+        super(name, language, scriptText);
     }
 
-
     @PluginFactory
-    public static Script createScript(@PluginAttribute("name") final String name,
-                                      @PluginAttribute("language") String language,
-                                      @PluginValue("scriptText") final String scriptText) {
+    public static Script createScript(
+            // @formatter:off
+            @PluginAttribute("name") final String name,
+            @PluginAttribute("language") String language,
+            @PluginValue("scriptText") final String scriptText) {
+            // @formatter:on
         if (language == null) {
-            logger.info("No script language supplied, defaulting to JavaScript");
-            language = "JavaScript";
+            logger.info("No script language supplied, defaulting to {}", DEFAULT_LANGUAGE);
+            language = DEFAULT_LANGUAGE;
         }
         if (scriptText == null) {
-            logger.error("No script provided");
+            logger.error("No scriptText attribute provided for ScriptFile {}", name);
             return null;
         }
         return new Script(name, language, scriptText);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
new file mode 100644
index 0000000..85497f6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
@@ -0,0 +1,64 @@
+package org.apache.logging.log4j.core.script;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.charset.Charset;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.core.util.IOUtils;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Container for the language and body of a script file.
+ */
+@Plugin(name = "ScriptFile", category = Node.CATEGORY, printObject = true)
+public class ScriptFile extends AbstractScript {
+
+    private static final Logger logger = StatusLogger.getLogger();
+
+    public ScriptFile(String name, String language, String scriptText) {
+        super(name, language, scriptText);
+    }
+
+    @PluginFactory
+    public static ScriptFile createScript(
+            // @formatter:off
+            @PluginAttribute("name") final String name,
+            @PluginAttribute("language") String language, 
+            @PluginAttribute("path") final String filePathOrUri,
+            @PluginAttribute("charset") final Charset charset) {
+            // @formatter:on
+        if (language == null) {
+            logger.info("No script language supplied, defaulting to {}", DEFAULT_LANGUAGE);
+            language = DEFAULT_LANGUAGE;
+        }
+        if (filePathOrUri == null) {
+            logger.error("No script path provided for ScriptFile {}", name);
+            return null;
+        }
+        final Charset actualCharset = charset == null ? Charset.defaultCharset() : charset;
+        final URI uri = NetUtils.toURI(filePathOrUri);
+        final File file = FileUtils.fileFromUri(uri);
+        String scriptText;
+        try (final Reader reader = file != null ? new FileReader(file)
+                : new InputStreamReader(uri.toURL().openStream(), actualCharset)) {
+            scriptText = IOUtils.toString(reader);
+        } catch (IOException e) {
+            logger.error("{}: name={}, language={}, path={}, actualCharset={}", e.getClass().getSimpleName(), name,
+                    language, filePathOrUri, actualCharset);
+            return null;
+        }
+        return new ScriptFile(name, language, scriptText);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
index a7b39cb..f189f9e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
@@ -82,7 +82,7 @@ public class ScriptManager {
         }
     }
 
-    public void addScript(final Script script) {
+    public void addScript(final AbstractScript script) {
         final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
         if (engine == null) {
             logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " +
@@ -111,12 +111,12 @@ public class ScriptManager {
     }
 
     private class MainScriptRunner implements ScriptRunner {
-        private final Script script;
+        private final AbstractScript script;
         private final CompiledScript compiledScript;
         private final ScriptEngine scriptEngine;
 
 
-        public MainScriptRunner(final ScriptEngine scriptEngine, final Script script) {
+        public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) {
             this.script = script;
             this.scriptEngine = scriptEngine;
             CompiledScript compiled = null;
@@ -155,7 +155,7 @@ public class ScriptManager {
     }
 
     private class ThreadLocalScriptRunner implements ScriptRunner {
-        private final Script script;
+        private final AbstractScript script;
 
         private final ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() {
             @Override protected MainScriptRunner initialValue() {
@@ -164,7 +164,7 @@ public class ScriptManager {
             }
         };
 
-        public ThreadLocalScriptRunner(final Script script) {
+        public ThreadLocalScriptRunner(final AbstractScript script) {
             this.script = script;
         }
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java
new file mode 100644
index 0000000..fcce59b
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/IOUtils.java
@@ -0,0 +1,114 @@
+package org.apache.logging.log4j.core.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * Copied from Apache Commons IO revision 1686747.
+ */
+public class IOUtils {
+
+    /**
+     * The default buffer size ({@value}) to use for
+     * {@link #copyLarge(InputStream, OutputStream)}
+     * and
+     * {@link #copyLarge(Reader, Writer)}
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+    /**
+     * Represents the end-of-file (or stream).
+     */
+    public static final int EOF = -1;
+
+    /**
+     * Copies chars from a <code>Reader</code> to a <code>Writer</code>.
+     * <p/>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p/>
+     * Large streams (over 2GB) will return a chars copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of chars cannot be returned as an int. For large streams
+     * use the <code>copyLarge(Reader, Writer)</code> method.
+     *
+     * @param input the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.1
+     */
+    public static int copy(final Reader input, final Writer output) throws IOException {
+        final long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    /**
+     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
+     * <p/>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p/>
+     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+     *
+     * @param input the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 1.3
+     */
+    public static long copyLarge(final Reader input, final Writer output) throws IOException {
+        return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
+    }
+
+    /**
+     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
+     * <p/>
+     * This method uses the provided buffer, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p/>
+     *
+     * @param input the <code>Reader</code> to read from
+     * @param output the <code>Writer</code> to write to
+     * @param buffer the buffer to be used for the copy
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException          if an I/O error occurs
+     * @since 2.2
+     */
+    public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException {
+        long count = 0;
+        int n;
+        while (EOF != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    /**
+     * Gets the contents of a <code>Reader</code> as a String.
+     * <p/>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input the <code>Reader</code> to read from
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException          if an I/O error occurs
+     */
+    public static String toString(final Reader input) throws IOException {
+        final StringBuilderWriter sw = new StringBuilderWriter();
+        copy(input, sw);
+        return sw.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/main/java/org/apache/logging/log4j/core/util/StringBuilderWriter.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/StringBuilderWriter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/StringBuilderWriter.java
new file mode 100644
index 0000000..588b492
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/StringBuilderWriter.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.util;
+
+import java.io.Serializable;
+import java.io.Writer;
+
+/**
+ * {@link Writer} implementation that outputs to a {@link StringBuilder}.
+ * <p>
+ * <strong>NOTE:</strong> This implementation, as an alternative to
+ * <code>java.io.StringWriter</code>, provides an <i>un-synchronized</i>
+ * (i.e. for use in a single thread) implementation for better performance.
+ * For safe usage with multiple {@link Thread}s then
+ * <code>java.io.StringWriter</code> should be used.
+ *
+ * Copied from Apache Commons IO revision 1681000.
+ */
+public class StringBuilderWriter extends Writer implements Serializable {
+
+    private static final long serialVersionUID = -146927496096066153L;
+    private final StringBuilder builder;
+
+    /**
+     * Construct a new {@link StringBuilder} instance with default capacity.
+     */
+    public StringBuilderWriter() {
+        this.builder = new StringBuilder();
+    }
+
+    /**
+     * Construct a new {@link StringBuilder} instance with the specified capacity.
+     *
+     * @param capacity The initial capacity of the underlying {@link StringBuilder}
+     */
+    public StringBuilderWriter(final int capacity) {
+        this.builder = new StringBuilder(capacity);
+    }
+
+    /**
+     * Construct a new instance with the specified {@link StringBuilder}.
+     * 
+     * <p>If {@code builder} is null a new instance with default capacity will be created.</p>
+     *
+     * @param builder The String builder. May be null.
+     */
+    public StringBuilderWriter(final StringBuilder builder) {
+        this.builder = builder != null ? builder : new StringBuilder();
+    }
+
+    /**
+     * Append a single character to this Writer.
+     *
+     * @param value The character to append
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final char value) {
+        builder.append(value);
+        return this;
+    }
+
+    /**
+     * Append a character sequence to this Writer.
+     *
+     * @param value The character to append
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final CharSequence value) {
+        builder.append(value);
+        return this;
+    }
+
+    /**
+     * Append a portion of a character sequence to the {@link StringBuilder}.
+     *
+     * @param value The character to append
+     * @param start The index of the first character
+     * @param end The index of the last character + 1
+     * @return This writer instance
+     */
+    @Override
+    public Writer append(final CharSequence value, final int start, final int end) {
+        builder.append(value, start, end);
+        return this;
+    }
+
+    /**
+     * Closing this writer has no effect. 
+     */
+    @Override
+    public void close() {
+        // no-op
+    }
+
+    /**
+     * Flushing this writer has no effect. 
+     */
+    @Override
+    public void flush() {
+        // no-op
+    }
+
+
+    /**
+     * Write a String to the {@link StringBuilder}.
+     * 
+     * @param value The value to write
+     */
+    @Override
+    public void write(final String value) {
+        if (value != null) {
+            builder.append(value);
+        }
+    }
+
+    /**
+     * Write a portion of a character array to the {@link StringBuilder}.
+     *
+     * @param value The value to write
+     * @param offset The index of the first character
+     * @param length The number of characters to write
+     */
+    @Override
+    public void write(final char[] value, final int offset, final int length) {
+        if (value != null) {
+            builder.append(value, offset, length);
+        }
+    }
+
+    /**
+     * Return the underlying builder.
+     *
+     * @return The underlying builder
+     */
+    public StringBuilder getBuilder() {
+        return builder;
+    }
+
+    /**
+     * Returns {@link StringBuilder#toString()}.
+     *
+     * @return The contents of the String builder.
+     */
+    @Override
+    public String toString() {
+        return builder.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java
new file mode 100644
index 0000000..bb5c4e1
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Test;
+
+/**
+ *
+ */
+public abstract class AbstractScriptFilterTest {
+
+    public abstract LoggerContextRule getContext();
+
+    @Test
+    public void testGroovyFilter() throws Exception {
+        Logger logger = LogManager.getLogger("TestGroovyFilter");
+        logger.entry();
+        logger.info("This should not be logged");
+        ThreadContext.put("UserId", "JohnDoe");
+        logger.info("This should be logged");
+        ThreadContext.clearMap();
+        final ListAppender app = (ListAppender) getContext().getRequiredAppender("List");
+        assertNotNull("No ListAppender", app);
+        try {
+            List<String> messages = app.getMessages();
+            assertNotNull("No Messages", messages);
+            assertTrue("Incorrect number of messages. Expected 2, Actual " + messages.size(), messages.size() == 2);
+        } finally {
+            app.clear();
+        }
+    }
+
+    @Test
+    public void testJavascriptFilter() throws Exception {
+        Logger logger = LogManager.getLogger("TestJavaScriptFilter");
+        logger.entry();
+        logger.info("This should not be logged");
+        ThreadContext.put("UserId", "JohnDoe");
+        logger.info("This should be logged");
+        ThreadContext.clearMap();
+        final ListAppender app = (ListAppender) getContext().getRequiredAppender("List");
+        assertNotNull("No ListAppender", app);
+        List<String> messages = app.getMessages();
+        try {
+            assertNotNull("No Messages", messages);
+            assertTrue("Incorrect number of messages. Expected 2, Actual " + messages.size(), messages.size() == 2);
+        } finally {
+            app.clear();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java
new file mode 100644
index 0000000..84e81b5
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.ClassRule;
+
+/**
+ *
+ */
+public class ScriptFileFilterTest extends AbstractScriptFilterTest {
+
+    private static final String CONFIG = "log4j-scriptFile-filters.xml";
+
+    @ClassRule
+    public static LoggerContextRule context = new LoggerContextRule(CONFIG);
+
+    @Override
+    public LoggerContextRule getContext() {
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java
index d138d89..4b515ca 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java
@@ -16,65 +16,22 @@
  */
 package org.apache.logging.log4j.core.filter;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.util.List;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 /**
  *
  */
-public class ScriptFilterTest {
+public class ScriptFilterTest extends AbstractScriptFilterTest {
 
-    private static final String CONFIG = "log4j-scriptFilters.xml";
+    private static final String CONFIG = "log4j-script-filters.xml";
 
     @ClassRule
     public static LoggerContextRule context = new LoggerContextRule(CONFIG);
 
-    @Test
-    public void testJavascriptFilter() throws Exception {
-        Logger logger = LogManager.getLogger("TestJavaScriptFilter");
-        logger.entry();
-        logger.info("This should not be logged");
-        ThreadContext.put("UserId", "JohnDoe");
-        logger.info("This should be logged");
-        ThreadContext.clearMap();
-        final ListAppender app = (ListAppender) context.getRequiredAppender("List");
-        assertNotNull("No ListAppender", app);
-        List<String> messages = app.getMessages();
-        try {
-            assertNotNull("No Messages", messages);
-            assertTrue("Incorrect number of messages. Expected 2, Actual " + messages.size(), messages.size() == 2);
-        } finally {
-            app.clear();
-        }
-    }
-
-    @Test
-    public void testGroovyFilter() throws Exception {
-        Logger logger = LogManager.getLogger("TestGroovyFilter");
-        logger.entry();
-        logger.info("This should not be logged");
-        ThreadContext.put("UserId", "JohnDoe");
-        logger.info("This should be logged");
-        ThreadContext.clearMap();
-        final ListAppender app = (ListAppender) context.getRequiredAppender("List");
-        assertNotNull("No ListAppender", app);
-        try {
-            List<String> messages = app.getMessages();
-            assertNotNull("No Messages", messages);
-            assertTrue("Incorrect number of messages. Expected 2, Actual " + messages.size(), messages.size() == 2);
-        } finally {
-            app.clear();
-        }
+    @Override
+    public LoggerContextRule getContext() {
+        return context;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/resources/log4j-script-filters.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-script-filters.xml b/log4j-core/src/test/resources/log4j-script-filters.xml
new file mode 100644
index 0000000..4168554
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-script-filters.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements. See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License. You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<Configuration status="ERROR">
+  <Appenders>
+    <List name="List">
+      <PatternLayout pattern="[%-5level] %c{1.} %msg%n"/>
+    </List>
+  </Appenders>
+  <Loggers>
+    <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
+      <AppenderRef ref="List">
+        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+          <Script name="JavascriptFilter" language="JavaScript"><![CDATA[
+            var result = false;
+            if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
+                result = true;
+            } else if (logEvent.getContextMap().containsKey("UserId")) {
+                result = true;
+            }
+            result;
+            ]]>
+          </Script>
+        </ScriptFilter>
+      </AppenderRef>
+    </Logger>
+    <Logger name="TestGroovyFilter" level="trace" additivity="false">
+      <AppenderRef ref="List">
+        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+          <Script name="GroovyFilter" 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>
+      </AppenderRef>
+    </Logger>
+    <Root level="trace">
+      <AppenderRef ref="List" />
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-scriptFile-filters.xml b/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
new file mode 100644
index 0000000..0fef7b5
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements. See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License. You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<Configuration status="ERROR">
+  <Appenders>
+    <List name="List">
+      <PatternLayout pattern="[%-5level] %c{1.} %msg%n"/>
+    </List>
+  </Appenders>
+  <Loggers>
+    <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
+      <AppenderRef ref="List">
+        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+          <ScriptFile name="JavascriptFilter" language="JavaScript" path="src/test/resources/scripts/filter.js" charset="UTF-8" />
+        </ScriptFilter>
+      </AppenderRef>
+    </Logger>
+    <Logger name="TestGroovyFilter" level="trace" additivity="false">
+      <AppenderRef ref="List">
+        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
+          <ScriptFile name="GroovyFilter" language="groovy" path="src/test/resources/scripts/filter.groovy" charset="UTF-8" />
+        </ScriptFilter>
+      </AppenderRef>
+    </Logger>
+    <Root level="trace">
+      <AppenderRef ref="List" />
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/resources/log4j-scriptFilters.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-scriptFilters.xml b/log4j-core/src/test/resources/log4j-scriptFilters.xml
deleted file mode 100644
index 4168554..0000000
--- a/log4j-core/src/test/resources/log4j-scriptFilters.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements. See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License. You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<Configuration status="ERROR">
-  <Appenders>
-    <List name="List">
-      <PatternLayout pattern="[%-5level] %c{1.} %msg%n"/>
-    </List>
-  </Appenders>
-  <Loggers>
-    <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
-      <AppenderRef ref="List">
-        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
-          <Script name="JavascriptFilter" language="JavaScript"><![CDATA[
-            var result = false;
-            if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
-                result = true;
-            } else if (logEvent.getContextMap().containsKey("UserId")) {
-                result = true;
-            }
-            result;
-            ]]>
-          </Script>
-        </ScriptFilter>
-      </AppenderRef>
-    </Logger>
-    <Logger name="TestGroovyFilter" level="trace" additivity="false">
-      <AppenderRef ref="List">
-        <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
-          <Script name="GroovyFilter" 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>
-      </AppenderRef>
-    </Logger>
-    <Root level="trace">
-      <AppenderRef ref="List" />
-    </Root>
-  </Loggers>
-</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/resources/scripts/filter.groovy
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/scripts/filter.groovy b/log4j-core/src/test/resources/scripts/filter.groovy
new file mode 100644
index 0000000..94d3b55
--- /dev/null
+++ b/log4j-core/src/test/resources/scripts/filter.groovy
@@ -0,0 +1,6 @@
+if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
+    return true;
+} else if (logEvent.getContextMap().containsKey("UserId")) {
+    return true;
+}
+return false;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/b3596bf9/log4j-core/src/test/resources/scripts/filter.js
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/scripts/filter.js b/log4j-core/src/test/resources/scripts/filter.js
new file mode 100644
index 0000000..3619d5f
--- /dev/null
+++ b/log4j-core/src/test/resources/scripts/filter.js
@@ -0,0 +1,7 @@
+var result = false;
+if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
+	result = true;
+} else if (logEvent.getContextMap().containsKey("UserId")) {
+	result = true;
+}
+result;