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 2016/09/14 05:00:03 UTC

logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 3846e2a87 -> e0f29d9ad


[LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
in a Routes element. 

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

Branch: refs/heads/master
Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
Parents: 3846e2a
Author: Gary Gregory <gg...@apache.org>
Authored: Tue Sep 13 21:59:59 2016 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Tue Sep 13 21:59:59 2016 -0700

----------------------------------------------------------------------
 .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
 .../core/appender/routing/RoutingAppender.java  |  19 ++-
 .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
 .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
 .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
 src/site/xdoc/manual/appenders.xml              |  27 +++-
 6 files changed, 340 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
index c95b64a..33fccd7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
@@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
 
 import java.util.Objects;
 
+import javax.script.SimpleBindings;
+
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configuration;
 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.PluginBuilderFactory;
+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.validation.constraints.Required;
+import org.apache.logging.log4j.core.script.AbstractScript;
 import org.apache.logging.log4j.status.StatusLogger;
 
 /**
@@ -33,54 +39,113 @@ public final class Routes {
 
     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
 
+        @PluginConfiguration 
+        private Configuration configuration;
+
         @PluginAttribute("pattern") 
         private String pattern;
         
-        @PluginElement("Routes") 
+        @PluginElement("Script")
+        private AbstractScript patternScript;
+
+        @PluginElement("Routes")
+        @Required
         private Route[] routes;
 
         @Override
         public Routes build() {
             if (routes == null || routes.length == 0) {
-                LOGGER.error("No routes configured");
+                LOGGER.error("No Routes configured.");
                 return null;
             }
-            return new Routes(pattern, routes);
+            if (patternScript != null && pattern != null) {
+                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
+            }
+            if (patternScript != null) {
+                if (configuration == null) {
+                    LOGGER.error("No Configuration defined for Routes; required for Script");
+                } else {
+                    configuration.getScriptManager().addScript(patternScript);
+                }
+            }
+            return new Routes(configuration, patternScript, pattern, routes);
+        }
+
+        public Configuration getConfiguration() {
+            return configuration;
         }
 
         public String getPattern() {
             return pattern;
         }
 
+        public AbstractScript getPatternScript() {
+            return patternScript;
+        }
+
         public Route[] getRoutes() {
             return routes;
         }
 
-        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
+        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
+            this.configuration = configuration;
+            return this;
+        }
+
+        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
             this.pattern = pattern;
             return this;
         }
 
-        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
+        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
+            this.patternScript = patternScript;
+            return this;
+        }
+
+        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
             this.routes = routes;
             return this;
         }
         
     }
 
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    /**
+     * Creates the Routes.
+     * @param pattern The pattern.
+     * @param routes An array of Route elements.
+     * @return The Routes container.
+     * @deprecated since 2.7; use {@link #newBuilder()}.
+     */
+    @Deprecated
+    public static Routes createRoutes(
+            final String pattern,
+            final Route... routes) {
+        if (routes == null || routes.length == 0) {
+            LOGGER.error("No routes configured");
+            return null;
+        }
+        return new Routes(null, null, pattern, routes);
+    }
+
     @PluginBuilderFactory
     public static Builder newBuilder() {
         return new Builder();
     }
-
-    private static final Logger LOGGER = StatusLogger.getLogger();
-
+    
+    private final Configuration configuration;
+    
     private final String pattern;
     
+    private final AbstractScript patternScript;
+
     // TODO Why not make this a Map or add a Map.
     private final Route[] routes;
 
-    private Routes(final String pattern, final Route... routes) {
+    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
+        this.configuration = configuration;
+        this.patternScript = patternScript;
         this.pattern = pattern;
         this.routes = routes;
     }
@@ -90,12 +155,26 @@ public final class Routes {
      * @return the pattern.
      */
     public String getPattern() {
+        if (patternScript != null) {
+            final SimpleBindings bindings = new SimpleBindings();
+            bindings.put("configuration", configuration);
+            bindings.put("statusLogger", LOGGER);
+            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
+            return Objects.toString(object, null);
+        }
         return pattern;
     }
 
-    public Route getRoute(String key) {
-        for (int i = 0; i < routes.length; i++) {
-            final Route route = routes[i];
+    /**
+     * Gets the optional script that decides which route to pick.
+     * @return the optional script that decides which route to pick. May be null.
+     */
+    public AbstractScript getPatternScript() {
+        return patternScript;
+    }
+
+    public Route getRoute(final String key) {
+        for (final Route route : routes) {
             if (Objects.equals(route.getKey(), key)) {
                 return route;
             }
@@ -127,22 +206,4 @@ public final class Routes {
 
     }
 
-    /**
-     * Creates the Routes.
-     * @param pattern The pattern.
-     * @param routes An array of Route elements.
-     * @return The Routes container.
-     * @deprecated since 2.7; use {@link #newBuilder()}.
-     */
-    @Deprecated
-    public static Routes createRoutes(
-            final String pattern,
-            final Route... routes) {
-        if (routes == null || routes.length == 0) {
-            LOGGER.error("No routes configured");
-            return null;
-        }
-        return new Routes(pattern, routes);
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
index 4471333..78fddbc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
@@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
         
         @Override
         public RoutingAppender build() {
-            if (getName() == null) {
-                LOGGER.error("No name defined for RoutingAppender");
+            final String name = getName();
+            if (name == null) {
+                LOGGER.error("No name defined for this RoutingAppender");
                 return null;
             }
             if (routes == null) {
-                LOGGER.error("No routes defined for RoutingAppender");
+                LOGGER.error("No routes defined for RoutingAppender {}", name);
                 return null;
             }
-            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
+            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
                     configuration, purgePolicy, defaultRouteScript);
         }
 
@@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
     public void start() {
         if (defaultRouteScript != null) {
             if (configuration == null) {
-                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
+                error("No Configuration defined for RoutingAppender; required for Script element.");
             } else {
                 configuration.getScriptManager().addScript(defaultRouteScript);
                 final SimpleBindings bindings = new SimpleBindings();
@@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
     public RewritePolicy getRewritePolicy() {
         return rewritePolicy;
     }
+
+    public Routes getRoutes() {
+        return routes;
+    }
+
+    public Configuration getConfiguration() {
+        return configuration;
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
new file mode 100644
index 0000000..7d90f6b
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.appender.routing;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.config.AppenderControl;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ *
+ */
+@RunWith(Parameterized.class)
+public class RoutesScriptAppenderTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static String[] getParameters() {
+        return new String[] { 
+                "log4j-routing-routes-script-groovy.xml",
+                "log4j-routing-routes-script-javascript.xml" };
+    }
+
+    @Rule
+    public final LoggerContextRule loggerContextRule;
+
+    public RoutesScriptAppenderTest(final String configLocation) {
+        this.loggerContextRule = new LoggerContextRule(configLocation);
+    }
+
+    private ListAppender getListAppender() {
+        final String key = "Service2";
+        final RoutingAppender routingAppender = getRoutingAppender();
+        Assert.assertTrue(routingAppender.isStarted());
+        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
+        final AppenderControl appenderControl = appenders.get(key);
+        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
+        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
+        return listAppender;
+    }
+
+    private RoutingAppender getRoutingAppender() {
+        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
+    }
+
+    private void logAndCheck() {
+        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
+        logger.error("Hello");
+        final ListAppender listAppender = getListAppender();
+        final List<LogEvent> list = listAppender.getEvents();
+        assertNotNull("No events generated", list);
+        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+        logger.error("World");
+        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void testAppenderAbsence() {
+        loggerContextRule.getListAppender("List1");
+    }
+
+    @Test
+    public void testListAppenderPresence() {
+        // No appender until an event is routed, even thought we initialized the default route on startup.
+        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
+    }
+
+    @Test
+    public void testNoPurgePolicy() {
+        // No PurgePolicy in this test
+        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
+    }
+
+    @Test
+    public void testNoRewritePolicy() {
+        // No RewritePolicy in this test
+        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
+    }
+
+    @Test
+    public void testRoutingAppenderRoutes() {
+        final RoutingAppender routingAppender = getRoutingAppender();
+        Assert.assertNull(routingAppender.getDefaultRouteScript());
+        Assert.assertNull(routingAppender.getDefaultRoute());
+        final Routes routes = routingAppender.getRoutes();
+        Assert.assertNotNull(routes);
+        Assert.assertNotNull(routes.getPatternScript());
+        Assert.assertEquals("Service2", routes.getPattern());
+    }
+
+    @Test
+    public void testRoutingAppenderPresence() {
+        getRoutingAppender();
+    }
+
+    @Test
+    public void testRoutingPresence1() {
+        logAndCheck();
+    }
+
+    @Test
+    public void testRoutingPresence2() {
+        logAndCheck();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
new file mode 100644
index 0000000..83121ea
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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="WARN" name="RoutingTest">
+  <Appenders>
+    <Routing name="Routing">
+      <Routes>
+        <Script name="RoutingInit" language="groovy"><![CDATA[
+          if ("OSNameFoo".contains("Foo")) {
+            return "Service2";
+          }
+          return "Service1";]]>
+        </Script>
+        <Route key="Service1">
+          <List name="List1" />
+        </Route>
+        <Route key="Service2">
+          <List name="List2" />
+        </Route>
+      </Routes>
+    </Routing>
+  </Appenders>
+  <Loggers>
+    <Root level="error">
+      <AppenderRef ref="Routing" />
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
new file mode 100644
index 0000000..e672aea
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
@@ -0,0 +1,40 @@
+<?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="WARN" name="RoutingTest">
+  <Appenders>
+    <Routing name="Routing">
+      <Routes>
+        <Script name="RoutingInit" language="JavaScript"><![CDATA[
+          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
+        </Script>
+        <Route key="Service1">
+          <List name="List1" />
+        </Route>
+        <Route key="Service2">
+          <List name="List2" />
+        </Route>
+      </Routes>
+    </Routing>
+  </Appenders>
+  <Loggers>
+    <Root level="error">
+      <AppenderRef ref="Routing" />
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index 2d3d361..a87d9b7 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
              Appender may be an appender previously configured and may be referenced by its name or the
              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
              Appenders it references to allow it to shut down properly.
-          </p>
+           </p>
+           <p>
+             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts 
+             and when a route is chosen for an log event. 
+           </p>
           <table>
             <caption align="top">RoutingAppender Parameters</caption>
             <tr>
@@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
               <th>Description</th>
             </tr>
             <tr>
-              <td>filter</td>
+              <td>Filter</td>
               <td>Filter</td>
               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
               may be used by using a CompositeFilter.</td>
@@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
               <td>The name of the Appender.</td>
             </tr>
             <tr>
-              <td>rewritePolicy</td>
+              <td>RewritePolicy</td>
               <td>RewritePolicy</td>
               <td>The RewritePolicy that will manipulate the LogEvent.</td>
             </tr>
             <tr>
-              <td>routes</td>
+              <td>Routes</td>
               <td>Routes</td>
               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
             </tr>
             <tr>
+              <td>Script</td>
+              <td>Script</td>
+              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine 
+                the default Route.</td>
+            </tr>
+            <tr>
               <td>ignoreExceptions</td>
               <td>boolean</td>
               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
@@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
           </table>
           <h4>Routes</h4>
             <p>
-              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
+              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
               against all the registered Lookups and the result is used to select a Route. Each Route may be
               configured with a key. If the key matches the result of evaluating the pattern then that Route
               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
               can be configured as the default.
             </p>
             <p>
+              The Routes element may contain a Script child element. If specified, the Script is run for each
+              log event and returns the String Route key to use.
+            </p>
+            <p>
+              You must specify either the pattern attribute or the Script element, but not both.
+            </p>
+            <p>
               Each Route must reference an Appender. If the Route contains a ref attribute then the
               Route will reference an Appender that was defined in the configuration. If the Route contains an
               Appender definition then an Appender will be created within the context of the RoutingAppender and


Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
I added a temporary "logEvent" variable to the Bindings for the Routes
Script.

Gary

On Wed, Sep 14, 2016 at 12:34 PM, Gary Gregory <ga...@gmail.com>
wrote:

> The RoutingAppender Scripts now share a Bindings instance which contains a
> ConcurrentMap keyed under "staticVariables". The Bindings instance is
> tracked as a RoutingAppender and Routes ivar.
>
> I created an abstract superclass for (private) ScriptRunner
> implementations which holds on to the ConcurrentMap. The map can act as a
> set of static/global variables for that script and can be shared through a
> Bindings instance. The private ScriptRunner has new method
> ScriptManager.ScriptRunner.createBindings(). Right now there is no script
> specific data added to the Bindings, but there could be in the future.
>
> I'll add LogEvent support next...
>
> Gary
>
> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ra...@dslextreme.com>
> wrote:
>
>> OK - It wasn’t in there when I looked last night.
>>
>> A couple other things. A ConcurrentMap should be created and passed to
>> the init script and the routing script so that the init script can pass
>> variables to the routing script. Also, the routing script really needs to
>> be passed the logEvent so it can route based on data within it.
>>
>> Ralph
>>
>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>> wrote:
>>
>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com
>> > wrote:
>>
>>> Gary,
>>>
>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for
>>> the Routes class to execute the script in the call to getPattern and return
>>> the result if there is a script?
>>>
>>
>> That's what Routes.getPattern() does (see this very commit thread):
>>
>> @@ -90,12 +155,26 @@ public final class Routes {
>>       * @return the pattern.
>>       */
>>      public String getPattern() {
>> +        if (patternScript != null) {
>> +            final SimpleBindings bindings = new SimpleBindings();
>> +            bindings.put("configuration", configuration);
>> +            bindings.put("statusLogger", LOGGER);
>> +            final Object object = configuration.getScriptManager
>> ().execute(patternScript.getName(), bindings);
>> +            return Objects.toString(object, null);
>> +        }
>>          return pattern;
>>      }
>>
>> Gary
>>
>>
>>>
>>> Ralph
>>>
>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>> >
>>> > Repository: logging-log4j2
>>> > Updated Branches:
>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>> >
>>> >
>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>> Script
>>> > in a Routes element.
>>> >
>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>> 2/commit/e0f29d9a
>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>> tree/e0f29d9a
>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>> diff/e0f29d9a
>>> >
>>> > Branch: refs/heads/master
>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>> > Parents: 3846e2a
>>> > Author: Gary Gregory <gg...@apache.org>
>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>> > Committer: Gary Gregory <gg...@apache.org>
>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>> >
>>> > ----------------------------------------------------------------------
>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>> ++++++++++++-----
>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>> +++++++++++++++++++
>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>> > ----------------------------------------------------------------------
>>> >
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>> core/appender/routing/Routes.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org
>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > index c95b64a..33fccd7 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>> appender.routing;
>>> >
>>> > import java.util.Objects;
>>> >
>>> > +import javax.script.SimpleBindings;
>>> > +
>>> > import org.apache.logging.log4j.Logger;
>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>> > 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.PluginBuilderFa
>>> ctory;
>>> > +import org.apache.logging.log4j.core.config.plugins.PluginConfigura
>>> tion;
>>> > import org.apache.logging.log4j.core.config.plugins.PluginElement;
>>> > +import org.apache.logging.log4j.core.config.plugins.validation.cons
>>> traints.Required;
>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>> > import org.apache.logging.log4j.status.StatusLogger;
>>> >
>>> > /**
>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>> >
>>> >     public static class Builder implements
>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>> >
>>> > +        @PluginConfiguration
>>> > +        private Configuration configuration;
>>> > +
>>> >         @PluginAttribute("pattern")
>>> >         private String pattern;
>>> >
>>> > -        @PluginElement("Routes")
>>> > +        @PluginElement("Script")
>>> > +        private AbstractScript patternScript;
>>> > +
>>> > +        @PluginElement("Routes")
>>> > +        @Required
>>> >         private Route[] routes;
>>> >
>>> >         @Override
>>> >         public Routes build() {
>>> >             if (routes == null || routes.length == 0) {
>>> > -                LOGGER.error("No routes configured");
>>> > +                LOGGER.error("No Routes configured.");
>>> >                 return null;
>>> >             }
>>> > -            return new Routes(pattern, routes);
>>> > +            if (patternScript != null && pattern != null) {
>>> > +                LOGGER.warn("In a Routes element, you must configure
>>> either a Script element or a pattern attribute.");
>>> > +            }
>>> > +            if (patternScript != null) {
>>> > +                if (configuration == null) {
>>> > +                    LOGGER.error("No Configuration defined for
>>> Routes; required for Script");
>>> > +                } else {
>>> > +                    configuration.getScriptManager
>>> ().addScript(patternScript);
>>> > +                }
>>> > +            }
>>> > +            return new Routes(configuration, patternScript, pattern,
>>> routes);
>>> > +        }
>>> > +
>>> > +        public Configuration getConfiguration() {
>>> > +            return configuration;
>>> >         }
>>> >
>>> >         public String getPattern() {
>>> >             return pattern;
>>> >         }
>>> >
>>> > +        public AbstractScript getPatternScript() {
>>> > +            return patternScript;
>>> > +        }
>>> > +
>>> >         public Route[] getRoutes() {
>>> >             return routes;
>>> >         }
>>> >
>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>> String pattern) {
>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>> final Configuration configuration) {
>>> > +            this.configuration = configuration;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final
>>> String pattern) {
>>> >             this.pattern = pattern;
>>> >             return this;
>>> >         }
>>> >
>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>> Route[] routes) {
>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>> final AbstractScript patternScript) {
>>> > +            this.patternScript = patternScript;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final
>>> Route[] routes) {
>>> >             this.routes = routes;
>>> >             return this;
>>> >         }
>>> >
>>> >     }
>>> >
>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > +
>>> > +    /**
>>> > +     * Creates the Routes.
>>> > +     * @param pattern The pattern.
>>> > +     * @param routes An array of Route elements.
>>> > +     * @return The Routes container.
>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > +     */
>>> > +    @Deprecated
>>> > +    public static Routes createRoutes(
>>> > +            final String pattern,
>>> > +            final Route... routes) {
>>> > +        if (routes == null || routes.length == 0) {
>>> > +            LOGGER.error("No routes configured");
>>> > +            return null;
>>> > +        }
>>> > +        return new Routes(null, null, pattern, routes);
>>> > +    }
>>> > +
>>> >     @PluginBuilderFactory
>>> >     public static Builder newBuilder() {
>>> >         return new Builder();
>>> >     }
>>> > -
>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > -
>>> > +
>>> > +    private final Configuration configuration;
>>> > +
>>> >     private final String pattern;
>>> >
>>> > +    private final AbstractScript patternScript;
>>> > +
>>> >     // TODO Why not make this a Map or add a Map.
>>> >     private final Route[] routes;
>>> >
>>> > -    private Routes(final String pattern, final Route... routes) {
>>> > +    private Routes(final Configuration configuration, final
>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>> > +        this.configuration = configuration;
>>> > +        this.patternScript = patternScript;
>>> >         this.pattern = pattern;
>>> >         this.routes = routes;
>>> >     }
>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>> >      * @return the pattern.
>>> >      */
>>> >     public String getPattern() {
>>> > +        if (patternScript != null) {
>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>> > +            bindings.put("configuration", configuration);
>>> > +            bindings.put("statusLogger", LOGGER);
>>> > +            final Object object = configuration.getScriptManager
>>> ().execute(patternScript.getName(), bindings);
>>> > +            return Objects.toString(object, null);
>>> > +        }
>>> >         return pattern;
>>> >     }
>>> >
>>> > -    public Route getRoute(String key) {
>>> > -        for (int i = 0; i < routes.length; i++) {
>>> > -            final Route route = routes[i];
>>> > +    /**
>>> > +     * Gets the optional script that decides which route to pick.
>>> > +     * @return the optional script that decides which route to pick.
>>> May be null.
>>> > +     */
>>> > +    public AbstractScript getPatternScript() {
>>> > +        return patternScript;
>>> > +    }
>>> > +
>>> > +    public Route getRoute(final String key) {
>>> > +        for (final Route route : routes) {
>>> >             if (Objects.equals(route.getKey(), key)) {
>>> >                 return route;
>>> >             }
>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>> >
>>> >     }
>>> >
>>> > -    /**
>>> > -     * Creates the Routes.
>>> > -     * @param pattern The pattern.
>>> > -     * @param routes An array of Route elements.
>>> > -     * @return The Routes container.
>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > -     */
>>> > -    @Deprecated
>>> > -    public static Routes createRoutes(
>>> > -            final String pattern,
>>> > -            final Route... routes) {
>>> > -        if (routes == null || routes.length == 0) {
>>> > -            LOGGER.error("No routes configured");
>>> > -            return null;
>>> > -        }
>>> > -        return new Routes(pattern, routes);
>>> > -    }
>>> > -
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>> core/appender/routing/RoutingAppender.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org
>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > index 4471333..78fddbc 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >
>>> >         @Override
>>> >         public RoutingAppender build() {
>>> > -            if (getName() == null) {
>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>> > +            final String name = getName();
>>> > +            if (name == null) {
>>> > +                LOGGER.error("No name defined for this
>>> RoutingAppender");
>>> >                 return null;
>>> >             }
>>> >             if (routes == null) {
>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>> > +                LOGGER.error("No routes defined for RoutingAppender
>>> {}", name);
>>> >                 return null;
>>> >             }
>>> > -            return new RoutingAppender(getName(), getFilter(),
>>> isIgnoreExceptions(), routes, rewritePolicy,
>>> > +            return new RoutingAppender(name, getFilter(),
>>> isIgnoreExceptions(), routes, rewritePolicy,
>>> >                     configuration, purgePolicy, defaultRouteScript);
>>> >         }
>>> >
>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >     public void start() {
>>> >         if (defaultRouteScript != null) {
>>> >             if (configuration == null) {
>>> > -                error("No Configuration defined for RoutingAppender;
>>> required for DefaultRouteScript");
>>> > +                error("No Configuration defined for RoutingAppender;
>>> required for Script element.");
>>> >             } else {
>>> >                 configuration.getScriptManage
>>> r().addScript(defaultRouteScript);
>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >     public RewritePolicy getRewritePolicy() {
>>> >         return rewritePolicy;
>>> >     }
>>> > +
>>> > +    public Routes getRoutes() {
>>> > +        return routes;
>>> > +    }
>>> > +
>>> > +    public Configuration getConfiguration() {
>>> > +        return configuration;
>>> > +    }
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>> core/appender/routing/RoutesScriptAppenderTest.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/java/org
>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutesScriptAppenderTest.java
>>> > new file mode 100644
>>> > index 0000000..7d90f6b
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutesScriptAppenderTest.java
>>> > @@ -0,0 +1,130 @@
>>> > +/*
>>> > + * 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.appender.routing;
>>> > +
>>> > +import static org.junit.Assert.assertNotNull;
>>> > +import static org.junit.Assert.assertTrue;
>>> > +
>>> > +import java.util.List;
>>> > +import java.util.Map;
>>> > +
>>> > +import org.apache.logging.log4j.core.LogEvent;
>>> > +import org.apache.logging.log4j.core.Logger;
>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>> > +import org.junit.Assert;
>>> > +import org.junit.Rule;
>>> > +import org.junit.Test;
>>> > +import org.junit.runner.RunWith;
>>> > +import org.junit.runners.Parameterized;
>>> > +
>>> > +/**
>>> > + *
>>> > + */
>>> > +@RunWith(Parameterized.class)
>>> > +public class RoutesScriptAppenderTest {
>>> > +
>>> > +    @Parameterized.Parameters(name = "{0}")
>>> > +    public static String[] getParameters() {
>>> > +        return new String[] {
>>> > +                "log4j-routing-routes-script-groovy.xml",
>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>> > +    }
>>> > +
>>> > +    @Rule
>>> > +    public final LoggerContextRule loggerContextRule;
>>> > +
>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>>> on);
>>> > +    }
>>> > +
>>> > +    private ListAppender getListAppender() {
>>> > +        final String key = "Service2";
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>> > +        final Map<String, AppenderControl> appenders =
>>> routingAppender.getAppenders();
>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>> > +        assertNotNull("No appender control generated for '" + key +
>>> "'; appenders = " + appenders, appenderControl);
>>> > +        final ListAppender listAppender = (ListAppender)
>>> appenderControl.getAppender();
>>> > +        return listAppender;
>>> > +    }
>>> > +
>>> > +    private RoutingAppender getRoutingAppender() {
>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>> RoutingAppender.class);
>>> > +    }
>>> > +
>>> > +    private void logAndCheck() {
>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>> utesScriptAppenderTest.class);
>>> > +        logger.error("Hello");
>>> > +        final ListAppender listAppender = getListAppender();
>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>> > +        assertNotNull("No events generated", list);
>>> > +        assertTrue("Incorrect number of events. Expected 1, got " +
>>> list.size(), list.size() == 1);
>>> > +        logger.error("World");
>>> > +        assertTrue("Incorrect number of events. Expected 2, got " +
>>> list.size(), list.size() == 2);
>>> > +    }
>>> > +
>>> > +    @Test(expected = AssertionError.class)
>>> > +    public void testAppenderAbsence() {
>>> > +        loggerContextRule.getListAppender("List1");
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testListAppenderPresence() {
>>> > +        // No appender until an event is routed, even thought we
>>> initialized the default route on startup.
>>> > +        Assert.assertNull("No appender control generated",
>>> getRoutingAppender().getAppenders().get("Service2"));
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoPurgePolicy() {
>>> > +        // No PurgePolicy in this test
>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>> getRoutingAppender().getPurgePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoRewritePolicy() {
>>> > +        // No RewritePolicy in this test
>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>> getRoutingAppender().getRewritePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderRoutes() {
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>> > +        final Routes routes = routingAppender.getRoutes();
>>> > +        Assert.assertNotNull(routes);
>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderPresence() {
>>> > +        getRoutingAppender();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence1() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence2() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +}
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>> -script-groovy.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-groovy.xml
>>> > new file mode 100644
>>> > index 0000000..83121ea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> groovy.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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>> > +          if ("OSNameFoo".contains("Foo")) {
>>> > +            return "Service2";
>>> > +          }
>>> > +          return "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>> -script-javascript.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-javascript.xml
>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> javascript.xml
>>> > new file mode 100644
>>> > index 0000000..e672aea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> javascript.xml
>>> > @@ -0,0 +1,40 @@
>>> > +<?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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>> b/src/site/xdoc/manual/appenders.xml
>>> > index 2d3d361..a87d9b7 100644
>>> > --- a/src/site/xdoc/manual/appenders.xml
>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >              Appender may be an appender previously configured and may
>>> be referenced by its name or the
>>> >              Appender can be dynamically created as needed. The
>>> RoutingAppender should be configured after any
>>> >              Appenders it references to allow it to shut down properly.
>>> > -          </p>
>>> > +           </p>
>>> > +           <p>
>>> > +             You can also configure a RoutingAppender with scripts:
>>> you can run a script when the appender starts
>>> > +             and when a route is chosen for an log event.
>>> > +           </p>
>>> >           <table>
>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>> >             <tr>
>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >               <th>Description</th>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>filter</td>
>>> > +              <td>Filter</td>
>>> >               <td>Filter</td>
>>> >               <td>A Filter to determine if the event should be handled
>>> by this Appender. More than one Filter
>>> >               may be used by using a CompositeFilter.</td>
>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >               <td>The name of the Appender.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>rewritePolicy</td>
>>> > +              <td>RewritePolicy</td>
>>> >               <td>RewritePolicy</td>
>>> >               <td>The RewritePolicy that will manipulate the
>>> LogEvent.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>routes</td>
>>> > +              <td>Routes</td>
>>> >               <td>Routes</td>
>>> >               <td>Contains one or more Route declarations to identify
>>> the criteria for choosing Appenders.</td>
>>> >             </tr>
>>> >             <tr>
>>> > +              <td>Script</td>
>>> > +              <td>Script</td>
>>> > +              <td>This Script runs when Log4j starts the
>>> RoutingAppender and returns a String Route key to determine
>>> > +                the default Route.</td>
>>> > +            </tr>
>>> > +            <tr>
>>> >               <td>ignoreExceptions</td>
>>> >               <td>boolean</td>
>>> >               <td>The default is <code>true</code>, causing exceptions
>>> encountered while appending events to be
>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >           </table>
>>> >           <h4>Routes</h4>
>>> >             <p>
>>> > -              The Routes element accepts a single, required attribute
>>> named "pattern". The pattern is evaluated
>>> > +              The Routes element accepts a single attribute named
>>> "pattern". The pattern is evaluated
>>> >               against all the registered Lookups and the result is
>>> used to select a Route. Each Route may be
>>> >               configured with a key. If the key matches the result of
>>> evaluating the pattern then that Route
>>> >               will be selected. If no key is specified on a Route then
>>> that Route is the default. Only one Route
>>> >               can be configured as the default.
>>> >             </p>
>>> >             <p>
>>> > +              The Routes element may contain a Script child element.
>>> If specified, the Script is run for each
>>> > +              log event and returns the String Route key to use.
>>> > +            </p>
>>> > +            <p>
>>> > +              You must specify either the pattern attribute or the
>>> Script element, but not both.
>>> > +            </p>
>>> > +            <p>
>>> >               Each Route must reference an Appender. If the Route
>>> contains a ref attribute then the
>>> >               Route will reference an Appender that was defined in the
>>> configuration. If the Route contains an
>>> >               Appender definition then an Appender will be created
>>> within the context of the RoutingAppender and
>>> >
>>> >
>>>
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>
>>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> <gg...@apache.org>
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>>
>>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>



-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
Once the Appender has been created the only additional overhead is the logic involved in routing to it.  I haven’t run any performance tests on it, but I’d be surprised if you would see much of a difference. OTOH, using Scripts to do the routing could get expensive if you use a language that doesn’t compile. 

Why are you worried about a concurrency issue when passing the LogEvent to a script?  The RoutingAppender synchronously calls getPattern() which synchronously executes the script. Since each script execution gets its own Binding there shouldn’t be a problem.

Ralph

> On Sep 14, 2016, at 2:24 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> Ralph: Thank you for the guidance on this topic.
> 
> I'll tackle the documentation update tonight. 
> 
> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
> 
> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
> 
> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
> 
> Gary
> 
> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> Sounds good.
> 
> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
> 
> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
> 
> Ralph
> 
>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>> 
>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>> 
>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>> 
>> I'll add LogEvent support next...
>> 
>> Gary
>> 
>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>> OK - It wasn’t in there when I looked last night.
>> 
>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>> 
>> Ralph
>> 
>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>> Gary,
>>> 
>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>> 
>>> That's what Routes.getPattern() does (see this very commit thread):
>>> 
>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>       * @return the pattern.
>>>       */
>>>      public String getPattern() {
>>> +        if (patternScript != null) {
>>> +            final SimpleBindings bindings = new SimpleBindings();
>>> +            bindings.put("configuration", configuration);
>>> +            bindings.put("statusLogger", LOGGER);
>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>> +            return Objects.toString(object, null);
>>> +        }
>>>          return pattern;
>>>      }
>>> 
>>> Gary
>>>  
>>> 
>>> Ralph
>>> 
>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>> >
>>> > Repository: logging-log4j2
>>> > Updated Branches:
>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>> >
>>> >
>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>> > in a Routes element.
>>> >
>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>> >
>>> > Branch: refs/heads/master
>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>> > Parents: 3846e2a
>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>> >
>>> > ----------------------------------------------------------------------
>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>> > ----------------------------------------------------------------------
>>> >
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>> > index c95b64a..33fccd7 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>> >
>>> > import java.util.Objects;
>>> >
>>> > +import javax.script.SimpleBindings;
>>> > +
>>> > import org.apache.logging.log4j.Logger;
>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>> > 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.PluginBuilderFactory;
>>> > +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.validation.constraints.Required;
>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>> > import org.apache.logging.log4j.status.StatusLogger;
>>> >
>>> > /**
>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>> >
>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>> >
>>> > +        @PluginConfiguration
>>> > +        private Configuration configuration;
>>> > +
>>> >         @PluginAttribute("pattern")
>>> >         private String pattern;
>>> >
>>> > -        @PluginElement("Routes")
>>> > +        @PluginElement("Script")
>>> > +        private AbstractScript patternScript;
>>> > +
>>> > +        @PluginElement("Routes")
>>> > +        @Required
>>> >         private Route[] routes;
>>> >
>>> >         @Override
>>> >         public Routes build() {
>>> >             if (routes == null || routes.length == 0) {
>>> > -                LOGGER.error("No routes configured");
>>> > +                LOGGER.error("No Routes configured.");
>>> >                 return null;
>>> >             }
>>> > -            return new Routes(pattern, routes);
>>> > +            if (patternScript != null && pattern != null) {
>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>> > +            }
>>> > +            if (patternScript != null) {
>>> > +                if (configuration == null) {
>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>> > +                } else {
>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>> > +                }
>>> > +            }
>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>> > +        }
>>> > +
>>> > +        public Configuration getConfiguration() {
>>> > +            return configuration;
>>> >         }
>>> >
>>> >         public String getPattern() {
>>> >             return pattern;
>>> >         }
>>> >
>>> > +        public AbstractScript getPatternScript() {
>>> > +            return patternScript;
>>> > +        }
>>> > +
>>> >         public Route[] getRoutes() {
>>> >             return routes;
>>> >         }
>>> >
>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>> > +            this.configuration = configuration;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>> >             this.pattern = pattern;
>>> >             return this;
>>> >         }
>>> >
>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>> > +            this.patternScript = patternScript;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>> >             this.routes = routes;
>>> >             return this;
>>> >         }
>>> >
>>> >     }
>>> >
>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > +
>>> > +    /**
>>> > +     * Creates the Routes.
>>> > +     * @param pattern The pattern.
>>> > +     * @param routes An array of Route elements.
>>> > +     * @return The Routes container.
>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > +     */
>>> > +    @Deprecated
>>> > +    public static Routes createRoutes(
>>> > +            final String pattern,
>>> > +            final Route... routes) {
>>> > +        if (routes == null || routes.length == 0) {
>>> > +            LOGGER.error("No routes configured");
>>> > +            return null;
>>> > +        }
>>> > +        return new Routes(null, null, pattern, routes);
>>> > +    }
>>> > +
>>> >     @PluginBuilderFactory
>>> >     public static Builder newBuilder() {
>>> >         return new Builder();
>>> >     }
>>> > -
>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > -
>>> > +
>>> > +    private final Configuration configuration;
>>> > +
>>> >     private final String pattern;
>>> >
>>> > +    private final AbstractScript patternScript;
>>> > +
>>> >     // TODO Why not make this a Map or add a Map.
>>> >     private final Route[] routes;
>>> >
>>> > -    private Routes(final String pattern, final Route... routes) {
>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>> > +        this.configuration = configuration;
>>> > +        this.patternScript = patternScript;
>>> >         this.pattern = pattern;
>>> >         this.routes = routes;
>>> >     }
>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>> >      * @return the pattern.
>>> >      */
>>> >     public String getPattern() {
>>> > +        if (patternScript != null) {
>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>> > +            bindings.put("configuration", configuration);
>>> > +            bindings.put("statusLogger", LOGGER);
>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>> > +            return Objects.toString(object, null);
>>> > +        }
>>> >         return pattern;
>>> >     }
>>> >
>>> > -    public Route getRoute(String key) {
>>> > -        for (int i = 0; i < routes.length; i++) {
>>> > -            final Route route = routes[i];
>>> > +    /**
>>> > +     * Gets the optional script that decides which route to pick.
>>> > +     * @return the optional script that decides which route to pick. May be null.
>>> > +     */
>>> > +    public AbstractScript getPatternScript() {
>>> > +        return patternScript;
>>> > +    }
>>> > +
>>> > +    public Route getRoute(final String key) {
>>> > +        for (final Route route : routes) {
>>> >             if (Objects.equals(route.getKey(), key)) {
>>> >                 return route;
>>> >             }
>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>> >
>>> >     }
>>> >
>>> > -    /**
>>> > -     * Creates the Routes.
>>> > -     * @param pattern The pattern.
>>> > -     * @param routes An array of Route elements.
>>> > -     * @return The Routes container.
>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > -     */
>>> > -    @Deprecated
>>> > -    public static Routes createRoutes(
>>> > -            final String pattern,
>>> > -            final Route... routes) {
>>> > -        if (routes == null || routes.length == 0) {
>>> > -            LOGGER.error("No routes configured");
>>> > -            return null;
>>> > -        }
>>> > -        return new Routes(pattern, routes);
>>> > -    }
>>> > -
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>> > index 4471333..78fddbc 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>> >
>>> >         @Override
>>> >         public RoutingAppender build() {
>>> > -            if (getName() == null) {
>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>> > +            final String name = getName();
>>> > +            if (name == null) {
>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>> >                 return null;
>>> >             }
>>> >             if (routes == null) {
>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>> >                 return null;
>>> >             }
>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>> >                     configuration, purgePolicy, defaultRouteScript);
>>> >         }
>>> >
>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>> >     public void start() {
>>> >         if (defaultRouteScript != null) {
>>> >             if (configuration == null) {
>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>> >             } else {
>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>> >     public RewritePolicy getRewritePolicy() {
>>> >         return rewritePolicy;
>>> >     }
>>> > +
>>> > +    public Routes getRoutes() {
>>> > +        return routes;
>>> > +    }
>>> > +
>>> > +    public Configuration getConfiguration() {
>>> > +        return configuration;
>>> > +    }
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>> > new file mode 100644
>>> > index 0000000..7d90f6b
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>> > @@ -0,0 +1,130 @@
>>> > +/*
>>> > + * 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 <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.appender.routing;
>>> > +
>>> > +import static org.junit.Assert.assertNotNull;
>>> > +import static org.junit.Assert.assertTrue;
>>> > +
>>> > +import java.util.List;
>>> > +import java.util.Map;
>>> > +
>>> > +import org.apache.logging.log4j.core.LogEvent;
>>> > +import org.apache.logging.log4j.core.Logger;
>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>> > +import org.junit.Assert;
>>> > +import org.junit.Rule;
>>> > +import org.junit.Test;
>>> > +import org.junit.runner.RunWith;
>>> > +import org.junit.runners.Parameterized;
>>> > +
>>> > +/**
>>> > + *
>>> > + */
>>> > +@RunWith(Parameterized.class)
>>> > +public class RoutesScriptAppenderTest {
>>> > +
>>> > +    @Parameterized.Parameters(name = "{0}")
>>> > +    public static String[] getParameters() {
>>> > +        return new String[] {
>>> > +                "log4j-routing-routes-script-groovy.xml",
>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>> > +    }
>>> > +
>>> > +    @Rule
>>> > +    public final LoggerContextRule loggerContextRule;
>>> > +
>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>> > +    }
>>> > +
>>> > +    private ListAppender getListAppender() {
>>> > +        final String key = "Service2";
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>> > +        return listAppender;
>>> > +    }
>>> > +
>>> > +    private RoutingAppender getRoutingAppender() {
>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>> > +    }
>>> > +
>>> > +    private void logAndCheck() {
>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>> > +        logger.error("Hello");
>>> > +        final ListAppender listAppender = getListAppender();
>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>> > +        assertNotNull("No events generated", list);
>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>> > +        logger.error("World");
>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>> > +    }
>>> > +
>>> > +    @Test(expected = AssertionError.class)
>>> > +    public void testAppenderAbsence() {
>>> > +        loggerContextRule.getListAppender("List1");
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testListAppenderPresence() {
>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoPurgePolicy() {
>>> > +        // No PurgePolicy in this test
>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoRewritePolicy() {
>>> > +        // No RewritePolicy in this test
>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderRoutes() {
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>> > +        final Routes routes = routingAppender.getRoutes();
>>> > +        Assert.assertNotNull(routes);
>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderPresence() {
>>> > +        getRoutingAppender();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence1() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence2() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +}
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>> > new file mode 100644
>>> > index 0000000..83121ea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>> > +          if ("OSNameFoo".contains("Foo")) {
>>> > +            return "Service2";
>>> > +          }
>>> > +          return "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>> > new file mode 100644
>>> > index 0000000..e672aea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>> > @@ -0,0 +1,40 @@
>>> > +<?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 <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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>> > ----------------------------------------------------------------------
>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>> > index 2d3d361..a87d9b7 100644
>>> > --- a/src/site/xdoc/manual/appenders.xml
>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>> >              Appenders it references to allow it to shut down properly.
>>> > -          </p>
>>> > +           </p>
>>> > +           <p>
>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>> > +             and when a route is chosen for an log event.
>>> > +           </p>
>>> >           <table>
>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>> >             <tr>
>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>> >               <th>Description</th>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>filter</td>
>>> > +              <td>Filter</td>
>>> >               <td>Filter</td>
>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>> >               may be used by using a CompositeFilter.</td>
>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>> >               <td>The name of the Appender.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>rewritePolicy</td>
>>> > +              <td>RewritePolicy</td>
>>> >               <td>RewritePolicy</td>
>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>routes</td>
>>> > +              <td>Routes</td>
>>> >               <td>Routes</td>
>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>> >             </tr>
>>> >             <tr>
>>> > +              <td>Script</td>
>>> > +              <td>Script</td>
>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>> > +                the default Route.</td>
>>> > +            </tr>
>>> > +            <tr>
>>> >               <td>ignoreExceptions</td>
>>> >               <td>boolean</td>
>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>> >           </table>
>>> >           <h4>Routes</h4>
>>> >             <p>
>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>> >               can be configured as the default.
>>> >             </p>
>>> >             <p>
>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>> > +              log event and returns the String Route key to use.
>>> > +            </p>
>>> > +            <p>
>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>> > +            </p>
>>> > +            <p>
>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>> >
>>> >
>>> 
>>> 
>>> 
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>> 
>>> 
>>> 
>>> 
>>> -- 
>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>> 
>> 
>> 
>> -- 
>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>> Home: http://garygregory.com/ <http://garygregory.com/>
>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
> 
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
I just looked at the code. Sure looks ok to me. I’ll look again….

Ralph

> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> That's not how I have now. I'll revisit...
> 
> 
> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> A new Binding is created for every script execution so thread safety is not a problem.
> 
> Ralph
> 
>> On Sep 14, 2016, at 4:09 PM, Remko Popma <remko.popma@gmail.com <ma...@gmail.com>> wrote:
>> 
>> If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 
>> 
>> Sent from my iPhone
>> 
>> On 2016/09/15, at 6:24, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>> 
>>> Ralph: Thank you for the guidance on this topic.
>>> 
>>> I'll tackle the documentation update tonight. 
>>> 
>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
>>> 
>>> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
>>> 
>>> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
>>> 
>>> Gary
>>> 
>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>> Sounds good.
>>> 
>>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>>> 
>>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
>>> 
>>> Ralph
>>> 
>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>>> 
>>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>>> 
>>>> I'll add LogEvent support next...
>>>> 
>>>> Gary
>>>> 
>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>> OK - It wasn’t in there when I looked last night.
>>>> 
>>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>>> 
>>>> Ralph
>>>> 
>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>> 
>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>> Gary,
>>>>> 
>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>>> 
>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>> 
>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>       * @return the pattern.
>>>>>       */
>>>>>      public String getPattern() {
>>>>> +        if (patternScript != null) {
>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>> +            bindings.put("configuration", configuration);
>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>> +            return Objects.toString(object, null);
>>>>> +        }
>>>>>          return pattern;
>>>>>      }
>>>>> 
>>>>> Gary
>>>>>  
>>>>> 
>>>>> Ralph
>>>>> 
>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>>>> >
>>>>> > Repository: logging-log4j2
>>>>> > Updated Branches:
>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>> >
>>>>> >
>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>>> > in a Routes element.
>>>>> >
>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>>>> >
>>>>> > Branch: refs/heads/master
>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>> > Parents: 3846e2a
>>>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>> >
>>>>> > ----------------------------------------------------------------------
>>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>> > ----------------------------------------------------------------------
>>>>> >
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > index c95b64a..33fccd7 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>>> >
>>>>> > import java.util.Objects;
>>>>> >
>>>>> > +import javax.script.SimpleBindings;
>>>>> > +
>>>>> > import org.apache.logging.log4j.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>> > 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.PluginBuilderFactory;
>>>>> > +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.validation.constraints.Required;
>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>> >
>>>>> > /**
>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>> >
>>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>> >
>>>>> > +        @PluginConfiguration
>>>>> > +        private Configuration configuration;
>>>>> > +
>>>>> >         @PluginAttribute("pattern")
>>>>> >         private String pattern;
>>>>> >
>>>>> > -        @PluginElement("Routes")
>>>>> > +        @PluginElement("Script")
>>>>> > +        private AbstractScript patternScript;
>>>>> > +
>>>>> > +        @PluginElement("Routes")
>>>>> > +        @Required
>>>>> >         private Route[] routes;
>>>>> >
>>>>> >         @Override
>>>>> >         public Routes build() {
>>>>> >             if (routes == null || routes.length == 0) {
>>>>> > -                LOGGER.error("No routes configured");
>>>>> > +                LOGGER.error("No Routes configured.");
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new Routes(pattern, routes);
>>>>> > +            if (patternScript != null && pattern != null) {
>>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>>> > +            }
>>>>> > +            if (patternScript != null) {
>>>>> > +                if (configuration == null) {
>>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>>> > +                } else {
>>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>>> > +                }
>>>>> > +            }
>>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>>> > +        }
>>>>> > +
>>>>> > +        public Configuration getConfiguration() {
>>>>> > +            return configuration;
>>>>> >         }
>>>>> >
>>>>> >         public String getPattern() {
>>>>> >             return pattern;
>>>>> >         }
>>>>> >
>>>>> > +        public AbstractScript getPatternScript() {
>>>>> > +            return patternScript;
>>>>> > +        }
>>>>> > +
>>>>> >         public Route[] getRoutes() {
>>>>> >             return routes;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>>> > +            this.configuration = configuration;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>>> >             this.pattern = pattern;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>>> > +            this.patternScript = patternScript;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>>> >             this.routes = routes;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > +
>>>>> > +    /**
>>>>> > +     * Creates the Routes.
>>>>> > +     * @param pattern The pattern.
>>>>> > +     * @param routes An array of Route elements.
>>>>> > +     * @return The Routes container.
>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > +     */
>>>>> > +    @Deprecated
>>>>> > +    public static Routes createRoutes(
>>>>> > +            final String pattern,
>>>>> > +            final Route... routes) {
>>>>> > +        if (routes == null || routes.length == 0) {
>>>>> > +            LOGGER.error("No routes configured");
>>>>> > +            return null;
>>>>> > +        }
>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>> > +    }
>>>>> > +
>>>>> >     @PluginBuilderFactory
>>>>> >     public static Builder newBuilder() {
>>>>> >         return new Builder();
>>>>> >     }
>>>>> > -
>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > -
>>>>> > +
>>>>> > +    private final Configuration configuration;
>>>>> > +
>>>>> >     private final String pattern;
>>>>> >
>>>>> > +    private final AbstractScript patternScript;
>>>>> > +
>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>> >     private final Route[] routes;
>>>>> >
>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>> > +        this.configuration = configuration;
>>>>> > +        this.patternScript = patternScript;
>>>>> >         this.pattern = pattern;
>>>>> >         this.routes = routes;
>>>>> >     }
>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>> >      * @return the pattern.
>>>>> >      */
>>>>> >     public String getPattern() {
>>>>> > +        if (patternScript != null) {
>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>> > +            bindings.put("configuration", configuration);
>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>> > +            return Objects.toString(object, null);
>>>>> > +        }
>>>>> >         return pattern;
>>>>> >     }
>>>>> >
>>>>> > -    public Route getRoute(String key) {
>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>> > -            final Route route = routes[i];
>>>>> > +    /**
>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>>> > +     */
>>>>> > +    public AbstractScript getPatternScript() {
>>>>> > +        return patternScript;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Route getRoute(final String key) {
>>>>> > +        for (final Route route : routes) {
>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>> >                 return route;
>>>>> >             }
>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > -    /**
>>>>> > -     * Creates the Routes.
>>>>> > -     * @param pattern The pattern.
>>>>> > -     * @param routes An array of Route elements.
>>>>> > -     * @return The Routes container.
>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > -     */
>>>>> > -    @Deprecated
>>>>> > -    public static Routes createRoutes(
>>>>> > -            final String pattern,
>>>>> > -            final Route... routes) {
>>>>> > -        if (routes == null || routes.length == 0) {
>>>>> > -            LOGGER.error("No routes configured");
>>>>> > -            return null;
>>>>> > -        }
>>>>> > -        return new Routes(pattern, routes);
>>>>> > -    }
>>>>> > -
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > index 4471333..78fddbc 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >
>>>>> >         @Override
>>>>> >         public RoutingAppender build() {
>>>>> > -            if (getName() == null) {
>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>> > +            final String name = getName();
>>>>> > +            if (name == null) {
>>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>>> >                 return null;
>>>>> >             }
>>>>> >             if (routes == null) {
>>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>> >         }
>>>>> >
>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >     public void start() {
>>>>> >         if (defaultRouteScript != null) {
>>>>> >             if (configuration == null) {
>>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>>> >             } else {
>>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>> >         return rewritePolicy;
>>>>> >     }
>>>>> > +
>>>>> > +    public Routes getRoutes() {
>>>>> > +        return routes;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Configuration getConfiguration() {
>>>>> > +        return configuration;
>>>>> > +    }
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>> > new file mode 100644
>>>>> > index 0000000..7d90f6b
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>> > @@ -0,0 +1,130 @@
>>>>> > +/*
>>>>> > + * 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 <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.appender.routing;
>>>>> > +
>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>> > +import static org.junit.Assert.assertTrue;
>>>>> > +
>>>>> > +import java.util.List;
>>>>> > +import java.util.Map;
>>>>> > +
>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>> > +import org.junit.Assert;
>>>>> > +import org.junit.Rule;
>>>>> > +import org.junit.Test;
>>>>> > +import org.junit.runner.RunWith;
>>>>> > +import org.junit.runners.Parameterized;
>>>>> > +
>>>>> > +/**
>>>>> > + *
>>>>> > + */
>>>>> > +@RunWith(Parameterized.class)
>>>>> > +public class RoutesScriptAppenderTest {
>>>>> > +
>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>> > +    public static String[] getParameters() {
>>>>> > +        return new String[] {
>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>> > +    }
>>>>> > +
>>>>> > +    @Rule
>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>> > +
>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>>> > +    }
>>>>> > +
>>>>> > +    private ListAppender getListAppender() {
>>>>> > +        final String key = "Service2";
>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>>> > +        return listAppender;
>>>>> > +    }
>>>>> > +
>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>>> > +    }
>>>>> > +
>>>>> > +    private void logAndCheck() {
>>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>>> > +        logger.error("Hello");
>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>> > +        assertNotNull("No events generated", list);
>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>>> > +        logger.error("World");
>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test(expected = AssertionError.class)
>>>>> > +    public void testAppenderAbsence() {
>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testListAppenderPresence() {
>>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoPurgePolicy() {
>>>>> > +        // No PurgePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoRewritePolicy() {
>>>>> > +        // No RewritePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>> > +        Assert.assertNotNull(routes);
>>>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderPresence() {
>>>>> > +        getRoutingAppender();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence1() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence2() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +}
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..83121ea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>> > +            return "Service2";
>>>>> > +          }
>>>>> > +          return "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..e672aea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>> > @@ -0,0 +1,40 @@
>>>>> > +<?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 <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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>>> > index 2d3d361..a87d9b7 100644
>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>>> >              Appenders it references to allow it to shut down properly.
>>>>> > -          </p>
>>>>> > +           </p>
>>>>> > +           <p>
>>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>>> > +             and when a route is chosen for an log event.
>>>>> > +           </p>
>>>>> >           <table>
>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>> >             <tr>
>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >               <th>Description</th>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>filter</td>
>>>>> > +              <td>Filter</td>
>>>>> >               <td>Filter</td>
>>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>>> >               may be used by using a CompositeFilter.</td>
>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >               <td>The name of the Appender.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>rewritePolicy</td>
>>>>> > +              <td>RewritePolicy</td>
>>>>> >               <td>RewritePolicy</td>
>>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>routes</td>
>>>>> > +              <td>Routes</td>
>>>>> >               <td>Routes</td>
>>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>>> > +                the default Route.</td>
>>>>> > +            </tr>
>>>>> > +            <tr>
>>>>> >               <td>ignoreExceptions</td>
>>>>> >               <td>boolean</td>
>>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >           </table>
>>>>> >           <h4>Routes</h4>
>>>>> >             <p>
>>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>>> >               can be configured as the default.
>>>>> >             </p>
>>>>> >             <p>
>>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>>> > +              log event and returns the String Route key to use.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>>> >
>>>>> >
>>>>> 
>>>>> 
>>>>> 
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>>>> 
>>>>> 
>>>>> 
>>>>> 
>>>>> -- 
>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>> 
>>>> 
>>>> 
>>>> -- 
>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>> 
>>> 
>>> 
>>> -- 
>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>


Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
Great, thanks for the review. As you said, let's deal with lookups later.

Gary

On Wed, Sep 14, 2016 at 9:31 PM, Ralph Goers <ra...@dslextreme.com>
wrote:

> This looks good to me. The only thing missing is the lookup and that can
> be added under a separate Jira issue.
>
> Ralph
>
> On Sep 14, 2016, at 7:12 PM, Gary Gregory <ga...@gmail.com> wrote:
>
> FYI: I just pushed site docs updates.
>
> Gary
>
> On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <ga...@gmail.com>
> wrote:
>
>> And I am not worried about concurrency around the log event.
>>
>> Gary
>>
>> On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <ga...@gmail.com>
>> wrote:
>>
>>> OK, I've just reworked the code. Bindings are no longer shared, only the
>>> ConcurrentMap is shared. Please review.
>>>
>>> Gary
>>>
>>> On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <ralph.goers@dslextreme.com
>>> > wrote:
>>>
>>>> And obviously the Bindings variable should not be a class member.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ra...@dslextreme.com>
>>>> wrote:
>>>>
>>>> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com>
>>>> wrote:
>>>>
>>>> That's not how I have now. I'll revisit...
>>>>
>>>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ra...@dslextreme.com>
>>>> wrote:
>>>>
>>>>> A new Binding is created for every script execution so thread safety
>>>>> is not a problem.
>>>>>
>>>>> Ralph
>>>>>
>>>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com>
>>>>> wrote:
>>>>>
>>>>> If the Binding is shared between threads (and therefore not
>>>>> thread-safe), you could put the LogEvent in a ThreadLocal.
>>>>>
>>>>> Sent from my iPhone
>>>>>
>>>>> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
>>>>>
>>>>> Ralph: Thank you for the guidance on this topic.
>>>>>
>>>>> I'll tackle the documentation update tonight.
>>>>>
>>>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on
>>>>> that, I can take a look.
>>>>>
>>>>> I am wondering in general about the performance difference from an app
>>>>> POV between a plain appender and one appender nested in a RoutingAppender.
>>>>>
>>>>> I am also wondering about any concurrency issue passing a LogEvent to
>>>>> a Script. Can a LogEvent be skipped when multiple threads use the same
>>>>> RoutingAppender?
>>>>>
>>>>> Gary
>>>>>
>>>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <
>>>>> ralph.goers@dslextreme.com> wrote:
>>>>>
>>>>>> Sounds good.
>>>>>>
>>>>>> When documenting that you should make it clear that the Map is shared
>>>>>> so that every thread is seeing the same Map. Users need to be aware that
>>>>>> they cannot put things in the map and expect it to only be available for
>>>>>> that single event.
>>>>>>
>>>>>> The last, and probably trickiest part, is going to be making it so
>>>>>> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
>>>>>> really figured out how to do that.
>>>>>>
>>>>>> Ralph
>>>>>>
>>>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com>
>>>>>> wrote:
>>>>>>
>>>>>> The RoutingAppender Scripts now share a Bindings instance which
>>>>>> contains a ConcurrentMap keyed under "staticVariables". The Bindings
>>>>>> instance is tracked as a RoutingAppender and Routes ivar.
>>>>>>
>>>>>> I created an abstract superclass for (private) ScriptRunner
>>>>>> implementations which holds on to the ConcurrentMap. The map can act as a
>>>>>> set of static/global variables for that script and can be shared through a
>>>>>> Bindings instance. The private ScriptRunner has new method
>>>>>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>>>>>> script specific data added to the Bindings, but there could be in the
>>>>>> future.
>>>>>>
>>>>>> I'll add LogEvent support next...
>>>>>>
>>>>>> Gary
>>>>>>
>>>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <
>>>>>> ralph.goers@dslextreme.com> wrote:
>>>>>>
>>>>>>> OK - It wasn’t in there when I looked last night.
>>>>>>>
>>>>>>> A couple other things. A ConcurrentMap should be created and passed
>>>>>>> to the init script and the routing script so that the init script can pass
>>>>>>> variables to the routing script. Also, the routing script really needs to
>>>>>>> be passed the logEvent so it can route based on data within it.
>>>>>>>
>>>>>>> Ralph
>>>>>>>
>>>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>>>>>>> wrote:
>>>>>>>
>>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <
>>>>>>> ralph.goers@dslextreme.com> wrote:
>>>>>>>
>>>>>>>> Gary,
>>>>>>>>
>>>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense
>>>>>>>> for the Routes class to execute the script in the call to getPattern and
>>>>>>>> return the result if there is a script?
>>>>>>>>
>>>>>>>
>>>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>>>
>>>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>>       * @return the pattern.
>>>>>>>       */
>>>>>>>      public String getPattern() {
>>>>>>> +        if (patternScript != null) {
>>>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>> +            bindings.put("configuration", configuration);
>>>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>>>> +            final Object object = configuration.getScriptManager
>>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>>> +            return Objects.toString(object, null);
>>>>>>> +        }
>>>>>>>          return pattern;
>>>>>>>      }
>>>>>>>
>>>>>>> Gary
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> Ralph
>>>>>>>>
>>>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>>>>>> >
>>>>>>>> > Repository: logging-log4j2
>>>>>>>> > Updated Branches:
>>>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>>>> >
>>>>>>>> >
>>>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>>>>>> Script
>>>>>>>> > in a Routes element.
>>>>>>>> >
>>>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4
>>>>>>>> j2/repo
>>>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>>>>>> 2/commit/e0f29d9a
>>>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>>> tree/e0f29d9a
>>>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>>> diff/e0f29d9a
>>>>>>>> >
>>>>>>>> > Branch: refs/heads/master
>>>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>>>> > Parents: 3846e2a
>>>>>>>> > Author: Gary Gregory <gg...@apache.org>
>>>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>>>> >
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>>>>>> ++++++++++++-----
>>>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>>>>>> +++++++++++++++++++
>>>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> >
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/Routes.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > index c95b64a..33fccd7 100644
>>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/Routes.java
>>>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>>>>>> appender.routing;
>>>>>>>> >
>>>>>>>> > import java.util.Objects;
>>>>>>>> >
>>>>>>>> > +import javax.script.SimpleBindings;
>>>>>>>> > +
>>>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>>>> > 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.PluginBuilderFactory;
>>>>>>>> > +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.validation.constraints.Required;
>>>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>>>> >
>>>>>>>> > /**
>>>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>>>> >
>>>>>>>> >     public static class Builder implements
>>>>>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>>>> >
>>>>>>>> > +        @PluginConfiguration
>>>>>>>> > +        private Configuration configuration;
>>>>>>>> > +
>>>>>>>> >         @PluginAttribute("pattern")
>>>>>>>> >         private String pattern;
>>>>>>>> >
>>>>>>>> > -        @PluginElement("Routes")
>>>>>>>> > +        @PluginElement("Script")
>>>>>>>> > +        private AbstractScript patternScript;
>>>>>>>> > +
>>>>>>>> > +        @PluginElement("Routes")
>>>>>>>> > +        @Required
>>>>>>>> >         private Route[] routes;
>>>>>>>> >
>>>>>>>> >         @Override
>>>>>>>> >         public Routes build() {
>>>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>>>> > -                LOGGER.error("No routes configured");
>>>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> > -            return new Routes(pattern, routes);
>>>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>>>> > +                LOGGER.warn("In a Routes element, you must
>>>>>>>> configure either a Script element or a pattern attribute.");
>>>>>>>> > +            }
>>>>>>>> > +            if (patternScript != null) {
>>>>>>>> > +                if (configuration == null) {
>>>>>>>> > +                    LOGGER.error("No Configuration defined for
>>>>>>>> Routes; required for Script");
>>>>>>>> > +                } else {
>>>>>>>> > +                    configuration.getScriptManager
>>>>>>>> ().addScript(patternScript);
>>>>>>>> > +                }
>>>>>>>> > +            }
>>>>>>>> > +            return new Routes(configuration, patternScript,
>>>>>>>> pattern, routes);
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Configuration getConfiguration() {
>>>>>>>> > +            return configuration;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> >         public String getPattern() {
>>>>>>>> >             return pattern;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>>>> > +            return patternScript;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> >         public Route[] getRoutes() {
>>>>>>>> >             return routes;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>>> String pattern) {
>>>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>>>>>> final Configuration configuration) {
>>>>>>>> > +            this.configuration = configuration;
>>>>>>>> > +            return this;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>>> final String pattern) {
>>>>>>>> >             this.pattern = pattern;
>>>>>>>> >             return this;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>>> Route[] routes) {
>>>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>>>>>> final AbstractScript patternScript) {
>>>>>>>> > +            this.patternScript = patternScript;
>>>>>>>> > +            return this;
>>>>>>>> > +        }
>>>>>>>> > +
>>>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>>> final Route[] routes) {
>>>>>>>> >             this.routes = routes;
>>>>>>>> >             return this;
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > +    private static final Logger LOGGER =
>>>>>>>> StatusLogger.getLogger();
>>>>>>>> > +
>>>>>>>> > +    /**
>>>>>>>> > +     * Creates the Routes.
>>>>>>>> > +     * @param pattern The pattern.
>>>>>>>> > +     * @param routes An array of Route elements.
>>>>>>>> > +     * @return The Routes container.
>>>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>>> > +     */
>>>>>>>> > +    @Deprecated
>>>>>>>> > +    public static Routes createRoutes(
>>>>>>>> > +            final String pattern,
>>>>>>>> > +            final Route... routes) {
>>>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>>>> > +            LOGGER.error("No routes configured");
>>>>>>>> > +            return null;
>>>>>>>> > +        }
>>>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> >     @PluginBuilderFactory
>>>>>>>> >     public static Builder newBuilder() {
>>>>>>>> >         return new Builder();
>>>>>>>> >     }
>>>>>>>> > -
>>>>>>>> > -    private static final Logger LOGGER =
>>>>>>>> StatusLogger.getLogger();
>>>>>>>> > -
>>>>>>>> > +
>>>>>>>> > +    private final Configuration configuration;
>>>>>>>> > +
>>>>>>>> >     private final String pattern;
>>>>>>>> >
>>>>>>>> > +    private final AbstractScript patternScript;
>>>>>>>> > +
>>>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>>>> >     private final Route[] routes;
>>>>>>>> >
>>>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>>>> > +    private Routes(final Configuration configuration, final
>>>>>>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>>>> > +        this.configuration = configuration;
>>>>>>>> > +        this.patternScript = patternScript;
>>>>>>>> >         this.pattern = pattern;
>>>>>>>> >         this.routes = routes;
>>>>>>>> >     }
>>>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>>> >      * @return the pattern.
>>>>>>>> >      */
>>>>>>>> >     public String getPattern() {
>>>>>>>> > +        if (patternScript != null) {
>>>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>>> > +            bindings.put("configuration", configuration);
>>>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>>>> > +            final Object object = configuration.getScriptManager
>>>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>>>> > +            return Objects.toString(object, null);
>>>>>>>> > +        }
>>>>>>>> >         return pattern;
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > -    public Route getRoute(String key) {
>>>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>>>> > -            final Route route = routes[i];
>>>>>>>> > +    /**
>>>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>>>> > +     * @return the optional script that decides which route to
>>>>>>>> pick. May be null.
>>>>>>>> > +     */
>>>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>>>> > +        return patternScript;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    public Route getRoute(final String key) {
>>>>>>>> > +        for (final Route route : routes) {
>>>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>>>> >                 return route;
>>>>>>>> >             }
>>>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>>>> >
>>>>>>>> >     }
>>>>>>>> >
>>>>>>>> > -    /**
>>>>>>>> > -     * Creates the Routes.
>>>>>>>> > -     * @param pattern The pattern.
>>>>>>>> > -     * @param routes An array of Route elements.
>>>>>>>> > -     * @return The Routes container.
>>>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>>> > -     */
>>>>>>>> > -    @Deprecated
>>>>>>>> > -    public static Routes createRoutes(
>>>>>>>> > -            final String pattern,
>>>>>>>> > -            final Route... routes) {
>>>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>>>> > -            LOGGER.error("No routes configured");
>>>>>>>> > -            return null;
>>>>>>>> > -        }
>>>>>>>> > -        return new Routes(pattern, routes);
>>>>>>>> > -    }
>>>>>>>> > -
>>>>>>>> > }
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/RoutingAppender.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > index 4471333..78fddbc 100644
>>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutingAppender.java
>>>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >
>>>>>>>> >         @Override
>>>>>>>> >         public RoutingAppender build() {
>>>>>>>> > -            if (getName() == null) {
>>>>>>>> > -                LOGGER.error("No name defined for
>>>>>>>> RoutingAppender");
>>>>>>>> > +            final String name = getName();
>>>>>>>> > +            if (name == null) {
>>>>>>>> > +                LOGGER.error("No name defined for this
>>>>>>>> RoutingAppender");
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> >             if (routes == null) {
>>>>>>>> > -                LOGGER.error("No routes defined for
>>>>>>>> RoutingAppender");
>>>>>>>> > +                LOGGER.error("No routes defined for
>>>>>>>> RoutingAppender {}", name);
>>>>>>>> >                 return null;
>>>>>>>> >             }
>>>>>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>>> > +            return new RoutingAppender(name, getFilter(),
>>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>>> >                     configuration, purgePolicy,
>>>>>>>> defaultRouteScript);
>>>>>>>> >         }
>>>>>>>> >
>>>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >     public void start() {
>>>>>>>> >         if (defaultRouteScript != null) {
>>>>>>>> >             if (configuration == null) {
>>>>>>>> > -                error("No Configuration defined for
>>>>>>>> RoutingAppender; required for DefaultRouteScript");
>>>>>>>> > +                error("No Configuration defined for
>>>>>>>> RoutingAppender; required for Script element.");
>>>>>>>> >             } else {
>>>>>>>> >                 configuration.getScriptManage
>>>>>>>> r().addScript(defaultRouteScript);
>>>>>>>> >                 final SimpleBindings bindings = new
>>>>>>>> SimpleBindings();
>>>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>>>>>> AbstractAppender {
>>>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>>>> >         return rewritePolicy;
>>>>>>>> >     }
>>>>>>>> > +
>>>>>>>> > +    public Routes getRoutes() {
>>>>>>>> > +        return routes;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    public Configuration getConfiguration() {
>>>>>>>> > +        return configuration;
>>>>>>>> > +    }
>>>>>>>> > }
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>>>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/java/org
>>>>>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..7d90f6b
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>>> > @@ -0,0 +1,130 @@
>>>>>>>> > +/*
>>>>>>>> > + * 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.appender.routing;
>>>>>>>> > +
>>>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>>>> > +
>>>>>>>> > +import java.util.List;
>>>>>>>> > +import java.util.Map;
>>>>>>>> > +
>>>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>>>> > +import org.junit.Assert;
>>>>>>>> > +import org.junit.Rule;
>>>>>>>> > +import org.junit.Test;
>>>>>>>> > +import org.junit.runner.RunWith;
>>>>>>>> > +import org.junit.runners.Parameterized;
>>>>>>>> > +
>>>>>>>> > +/**
>>>>>>>> > + *
>>>>>>>> > + */
>>>>>>>> > +@RunWith(Parameterized.class)
>>>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>>>> > +
>>>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>>>> > +    public static String[] getParameters() {
>>>>>>>> > +        return new String[] {
>>>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Rule
>>>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>>>> > +
>>>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation)
>>>>>>>> {
>>>>>>>> > +        this.loggerContextRule = new
>>>>>>>> LoggerContextRule(configLocation);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private ListAppender getListAppender() {
>>>>>>>> > +        final String key = "Service2";
>>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>>> getRoutingAppender();
>>>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>>>> > +        final Map<String, AppenderControl> appenders =
>>>>>>>> routingAppender.getAppenders();
>>>>>>>> > +        final AppenderControl appenderControl =
>>>>>>>> appenders.get(key);
>>>>>>>> > +        assertNotNull("No appender control generated for '" +
>>>>>>>> key + "'; appenders = " + appenders, appenderControl);
>>>>>>>> > +        final ListAppender listAppender = (ListAppender)
>>>>>>>> appenderControl.getAppender();
>>>>>>>> > +        return listAppender;
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>>>>>> RoutingAppender.class);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    private void logAndCheck() {
>>>>>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>>>>>> utesScriptAppenderTest.class);
>>>>>>>> > +        logger.error("Hello");
>>>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>>>> > +        assertNotNull("No events generated", list);
>>>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got
>>>>>>>> " + list.size(), list.size() == 1);
>>>>>>>> > +        logger.error("World");
>>>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got
>>>>>>>> " + list.size(), list.size() == 2);
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>>>> > +    public void testAppenderAbsence() {
>>>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testListAppenderPresence() {
>>>>>>>> > +        // No appender until an event is routed, even thought we
>>>>>>>> initialized the default route on startup.
>>>>>>>> > +        Assert.assertNull("No appender control generated",
>>>>>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testNoPurgePolicy() {
>>>>>>>> > +        // No PurgePolicy in this test
>>>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>>>>>> getRoutingAppender().getPurgePolicy());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testNoRewritePolicy() {
>>>>>>>> > +        // No RewritePolicy in this test
>>>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>>>>>> getRoutingAppender().getRewritePolicy());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>>> getRoutingAppender();
>>>>>>>> > +        Assert.assertNull(routingAppen
>>>>>>>> der.getDefaultRouteScript());
>>>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>>>> > +        Assert.assertNotNull(routes);
>>>>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>>>> > +        getRoutingAppender();
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingPresence1() {
>>>>>>>> > +        logAndCheck();
>>>>>>>> > +    }
>>>>>>>> > +
>>>>>>>> > +    @Test
>>>>>>>> > +    public void testRoutingPresence2() {
>>>>>>>> > +        logAndCheck();
>>>>>>>> > +    }
>>>>>>>> > +}
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>>> -script-groovy.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>>> s/log4j-routing-routes-script-groovy.xml
>>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> groovy.xml
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..83121ea
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> groovy.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="WARN" name="RoutingTest">
>>>>>>>> > +  <Appenders>
>>>>>>>> > +    <Routing name="Routing">
>>>>>>>> > +      <Routes>
>>>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>>>> > +            return "Service2";
>>>>>>>> > +          }
>>>>>>>> > +          return "Service1";]]>
>>>>>>>> > +        </Script>
>>>>>>>> > +        <Route key="Service1">
>>>>>>>> > +          <List name="List1" />
>>>>>>>> > +        </Route>
>>>>>>>> > +        <Route key="Service2">
>>>>>>>> > +          <List name="List2" />
>>>>>>>> > +        </Route>
>>>>>>>> > +      </Routes>
>>>>>>>> > +    </Routing>
>>>>>>>> > +  </Appenders>
>>>>>>>> > +  <Loggers>
>>>>>>>> > +    <Root level="error">
>>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>>> > +    </Root>
>>>>>>>> > +  </Loggers>
>>>>>>>> > +</Configuration>
>>>>>>>> > \ No newline at end of file
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>>> -script-javascript.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>>> s/log4j-routing-routes-script-javascript.xml
>>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> javascript.xml
>>>>>>>> > new file mode 100644
>>>>>>>> > index 0000000..e672aea
>>>>>>>> > --- /dev/null
>>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>>> javascript.xml
>>>>>>>> > @@ -0,0 +1,40 @@
>>>>>>>> > +<?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="WARN" name="RoutingTest">
>>>>>>>> > +  <Appenders>
>>>>>>>> > +    <Routing name="Routing">
>>>>>>>> > +      <Routes>
>>>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA
>>>>>>>> [
>>>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>>>>>> "Service1";]]>
>>>>>>>> > +        </Script>
>>>>>>>> > +        <Route key="Service1">
>>>>>>>> > +          <List name="List1" />
>>>>>>>> > +        </Route>
>>>>>>>> > +        <Route key="Service2">
>>>>>>>> > +          <List name="List2" />
>>>>>>>> > +        </Route>
>>>>>>>> > +      </Routes>
>>>>>>>> > +    </Routing>
>>>>>>>> > +  </Appenders>
>>>>>>>> > +  <Loggers>
>>>>>>>> > +    <Root level="error">
>>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>>> > +    </Root>
>>>>>>>> > +  </Loggers>
>>>>>>>> > +</Configuration>
>>>>>>>> > \ No newline at end of file
>>>>>>>> >
>>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>>>>> > ------------------------------------------------------------
>>>>>>>> ----------
>>>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>>>>>> b/src/site/xdoc/manual/appenders.xml
>>>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >              Appender may be an appender previously configured
>>>>>>>> and may be referenced by its name or the
>>>>>>>> >              Appender can be dynamically created as needed. The
>>>>>>>> RoutingAppender should be configured after any
>>>>>>>> >              Appenders it references to allow it to shut down
>>>>>>>> properly.
>>>>>>>> > -          </p>
>>>>>>>> > +           </p>
>>>>>>>> > +           <p>
>>>>>>>> > +             You can also configure a RoutingAppender with
>>>>>>>> scripts: you can run a script when the appender starts
>>>>>>>> > +             and when a route is chosen for an log event.
>>>>>>>> > +           </p>
>>>>>>>> >           <table>
>>>>>>>> >             <caption align="top">RoutingAppender
>>>>>>>> Parameters</caption>
>>>>>>>> >             <tr>
>>>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >               <th>Description</th>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>filter</td>
>>>>>>>> > +              <td>Filter</td>
>>>>>>>> >               <td>Filter</td>
>>>>>>>> >               <td>A Filter to determine if the event should be
>>>>>>>> handled by this Appender. More than one Filter
>>>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >               <td>The name of the Appender.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>rewritePolicy</td>
>>>>>>>> > +              <td>RewritePolicy</td>
>>>>>>>> >               <td>RewritePolicy</td>
>>>>>>>> >               <td>The RewritePolicy that will manipulate the
>>>>>>>> LogEvent.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > -              <td>routes</td>
>>>>>>>> > +              <td>Routes</td>
>>>>>>>> >               <td>Routes</td>
>>>>>>>> >               <td>Contains one or more Route declarations to
>>>>>>>> identify the criteria for choosing Appenders.</td>
>>>>>>>> >             </tr>
>>>>>>>> >             <tr>
>>>>>>>> > +              <td>Script</td>
>>>>>>>> > +              <td>Script</td>
>>>>>>>> > +              <td>This Script runs when Log4j starts the
>>>>>>>> RoutingAppender and returns a String Route key to determine
>>>>>>>> > +                the default Route.</td>
>>>>>>>> > +            </tr>
>>>>>>>> > +            <tr>
>>>>>>>> >               <td>ignoreExceptions</td>
>>>>>>>> >               <td>boolean</td>
>>>>>>>> >               <td>The default is <code>true</code>, causing
>>>>>>>> exceptions encountered while appending events to be
>>>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>>> >           </table>
>>>>>>>> >           <h4>Routes</h4>
>>>>>>>> >             <p>
>>>>>>>> > -              The Routes element accepts a single, required
>>>>>>>> attribute named "pattern". The pattern is evaluated
>>>>>>>> > +              The Routes element accepts a single attribute
>>>>>>>> named "pattern". The pattern is evaluated
>>>>>>>> >               against all the registered Lookups and the result
>>>>>>>> is used to select a Route. Each Route may be
>>>>>>>> >               configured with a key. If the key matches the
>>>>>>>> result of evaluating the pattern then that Route
>>>>>>>> >               will be selected. If no key is specified on a Route
>>>>>>>> then that Route is the default. Only one Route
>>>>>>>> >               can be configured as the default.
>>>>>>>> >             </p>
>>>>>>>> >             <p>
>>>>>>>> > +              The Routes element may contain a Script child
>>>>>>>> element. If specified, the Script is run for each
>>>>>>>> > +              log event and returns the String Route key to use.
>>>>>>>> > +            </p>
>>>>>>>> > +            <p>
>>>>>>>> > +              You must specify either the pattern attribute or
>>>>>>>> the Script element, but not both.
>>>>>>>> > +            </p>
>>>>>>>> > +            <p>
>>>>>>>> >               Each Route must reference an Appender. If the Route
>>>>>>>> contains a ref attribute then the
>>>>>>>> >               Route will reference an Appender that was defined
>>>>>>>> in the configuration. If the Route contains an
>>>>>>>> >               Appender definition then an Appender will be
>>>>>>>> created within the context of the RoutingAppender and
>>>>>>>> >
>>>>>>>> >
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> ------------------------------------------------------------
>>>>>>>> ---------
>>>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>>>> <gg...@apache.org>
>>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>>> <http://www.manning.com/bauer3/>
>>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>>> Blog: http://garygregory.wordpress.com
>>>>>>> Home: http://garygregory.com/
>>>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>> <http://www.manning.com/bauer3/>
>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>> Blog: http://garygregory.wordpress.com
>>>>>> Home: http://garygregory.com/
>>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>> Java Persistence with Hibernate, Second Edition
>>>>> <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com
>>>>> Home: http://garygregory.com/
>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>> Java Persistence with Hibernate, Second Edition
>>> <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
>>>
>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>
>
>


-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
This looks good to me. The only thing missing is the lookup and that can be added under a separate Jira issue. 

Ralph

> On Sep 14, 2016, at 7:12 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> FYI: I just pushed site docs updates.
> 
> Gary
> 
> On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
> And I am not worried about concurrency around the log event.
> 
> Gary
> 
> On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
> OK, I've just reworked the code. Bindings are no longer shared, only the ConcurrentMap is shared. Please review.
> 
> Gary
> 
> On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> And obviously the Bindings variable should not be a class member.
> 
> Ralph
> 
>> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>> 
>> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>> 
>> Ralph
>> 
>>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>> That's not how I have now. I'll revisit...
>>> 
>>> 
>>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>> A new Binding is created for every script execution so thread safety is not a problem.
>>> 
>>> Ralph
>>> 
>>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <remko.popma@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>> If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 
>>>> 
>>>> Sent from my iPhone
>>>> 
>>>> On 2016/09/15, at 6:24, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>>> Ralph: Thank you for the guidance on this topic.
>>>>> 
>>>>> I'll tackle the documentation update tonight. 
>>>>> 
>>>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
>>>>> 
>>>>> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
>>>>> 
>>>>> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
>>>>> 
>>>>> Gary
>>>>> 
>>>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>> Sounds good.
>>>>> 
>>>>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>>>>> 
>>>>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
>>>>> 
>>>>> Ralph
>>>>> 
>>>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>>> 
>>>>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>>>>> 
>>>>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>>>>> 
>>>>>> I'll add LogEvent support next...
>>>>>> 
>>>>>> Gary
>>>>>> 
>>>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>>> OK - It wasn’t in there when I looked last night.
>>>>>> 
>>>>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>>>>> 
>>>>>> Ralph
>>>>>> 
>>>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>>>> 
>>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>>>> Gary,
>>>>>>> 
>>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>>>>> 
>>>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>>> 
>>>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>>       * @return the pattern.
>>>>>>>       */
>>>>>>>      public String getPattern() {
>>>>>>> +        if (patternScript != null) {
>>>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>> +            bindings.put("configuration", configuration);
>>>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>>>> +            return Objects.toString(object, null);
>>>>>>> +        }
>>>>>>>          return pattern;
>>>>>>>      }
>>>>>>> 
>>>>>>> Gary
>>>>>>>  
>>>>>>> 
>>>>>>> Ralph
>>>>>>> 
>>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>>>>>> >
>>>>>>> > Repository: logging-log4j2
>>>>>>> > Updated Branches:
>>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>>> >
>>>>>>> >
>>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>>>>> > in a Routes element.
>>>>>>> >
>>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>>>>>> >
>>>>>>> > Branch: refs/heads/master
>>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>>> > Parents: 3846e2a
>>>>>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>>> >
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> >
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>> > index c95b64a..33fccd7 100644
>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>>>>> >
>>>>>>> > import java.util.Objects;
>>>>>>> >
>>>>>>> > +import javax.script.SimpleBindings;
>>>>>>> > +
>>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>>> > 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.PluginBuilderFactory;
>>>>>>> > +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.validation.constraints.Required;
>>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>>> >
>>>>>>> > /**
>>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>>> >
>>>>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>>> >
>>>>>>> > +        @PluginConfiguration
>>>>>>> > +        private Configuration configuration;
>>>>>>> > +
>>>>>>> >         @PluginAttribute("pattern")
>>>>>>> >         private String pattern;
>>>>>>> >
>>>>>>> > -        @PluginElement("Routes")
>>>>>>> > +        @PluginElement("Script")
>>>>>>> > +        private AbstractScript patternScript;
>>>>>>> > +
>>>>>>> > +        @PluginElement("Routes")
>>>>>>> > +        @Required
>>>>>>> >         private Route[] routes;
>>>>>>> >
>>>>>>> >         @Override
>>>>>>> >         public Routes build() {
>>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>>> > -                LOGGER.error("No routes configured");
>>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> > -            return new Routes(pattern, routes);
>>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>>>>> > +            }
>>>>>>> > +            if (patternScript != null) {
>>>>>>> > +                if (configuration == null) {
>>>>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>>>>> > +                } else {
>>>>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>>>>> > +                }
>>>>>>> > +            }
>>>>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Configuration getConfiguration() {
>>>>>>> > +            return configuration;
>>>>>>> >         }
>>>>>>> >
>>>>>>> >         public String getPattern() {
>>>>>>> >             return pattern;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>>> > +            return patternScript;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> >         public Route[] getRoutes() {
>>>>>>> >             return routes;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>>>>> > +            this.configuration = configuration;
>>>>>>> > +            return this;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>>>>> >             this.pattern = pattern;
>>>>>>> >             return this;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>>>>> > +            this.patternScript = patternScript;
>>>>>>> > +            return this;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>>>>> >             this.routes = routes;
>>>>>>> >             return this;
>>>>>>> >         }
>>>>>>> >
>>>>>>> >     }
>>>>>>> >
>>>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>>> > +
>>>>>>> > +    /**
>>>>>>> > +     * Creates the Routes.
>>>>>>> > +     * @param pattern The pattern.
>>>>>>> > +     * @param routes An array of Route elements.
>>>>>>> > +     * @return The Routes container.
>>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>> > +     */
>>>>>>> > +    @Deprecated
>>>>>>> > +    public static Routes createRoutes(
>>>>>>> > +            final String pattern,
>>>>>>> > +            final Route... routes) {
>>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>>> > +            LOGGER.error("No routes configured");
>>>>>>> > +            return null;
>>>>>>> > +        }
>>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> >     @PluginBuilderFactory
>>>>>>> >     public static Builder newBuilder() {
>>>>>>> >         return new Builder();
>>>>>>> >     }
>>>>>>> > -
>>>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>>> > -
>>>>>>> > +
>>>>>>> > +    private final Configuration configuration;
>>>>>>> > +
>>>>>>> >     private final String pattern;
>>>>>>> >
>>>>>>> > +    private final AbstractScript patternScript;
>>>>>>> > +
>>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>>> >     private final Route[] routes;
>>>>>>> >
>>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>>> > +        this.configuration = configuration;
>>>>>>> > +        this.patternScript = patternScript;
>>>>>>> >         this.pattern = pattern;
>>>>>>> >         this.routes = routes;
>>>>>>> >     }
>>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>> >      * @return the pattern.
>>>>>>> >      */
>>>>>>> >     public String getPattern() {
>>>>>>> > +        if (patternScript != null) {
>>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>> > +            bindings.put("configuration", configuration);
>>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>>>> > +            return Objects.toString(object, null);
>>>>>>> > +        }
>>>>>>> >         return pattern;
>>>>>>> >     }
>>>>>>> >
>>>>>>> > -    public Route getRoute(String key) {
>>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>>> > -            final Route route = routes[i];
>>>>>>> > +    /**
>>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>>>>> > +     */
>>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>>> > +        return patternScript;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    public Route getRoute(final String key) {
>>>>>>> > +        for (final Route route : routes) {
>>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>>> >                 return route;
>>>>>>> >             }
>>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>>> >
>>>>>>> >     }
>>>>>>> >
>>>>>>> > -    /**
>>>>>>> > -     * Creates the Routes.
>>>>>>> > -     * @param pattern The pattern.
>>>>>>> > -     * @param routes An array of Route elements.
>>>>>>> > -     * @return The Routes container.
>>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>> > -     */
>>>>>>> > -    @Deprecated
>>>>>>> > -    public static Routes createRoutes(
>>>>>>> > -            final String pattern,
>>>>>>> > -            final Route... routes) {
>>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>>> > -            LOGGER.error("No routes configured");
>>>>>>> > -            return null;
>>>>>>> > -        }
>>>>>>> > -        return new Routes(pattern, routes);
>>>>>>> > -    }
>>>>>>> > -
>>>>>>> > }
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>> > index 4471333..78fddbc 100644
>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>>> >
>>>>>>> >         @Override
>>>>>>> >         public RoutingAppender build() {
>>>>>>> > -            if (getName() == null) {
>>>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>>>> > +            final String name = getName();
>>>>>>> > +            if (name == null) {
>>>>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> >             if (routes == null) {
>>>>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>>>> >         }
>>>>>>> >
>>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>>> >     public void start() {
>>>>>>> >         if (defaultRouteScript != null) {
>>>>>>> >             if (configuration == null) {
>>>>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>>>>> >             } else {
>>>>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>>> >         return rewritePolicy;
>>>>>>> >     }
>>>>>>> > +
>>>>>>> > +    public Routes getRoutes() {
>>>>>>> > +        return routes;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    public Configuration getConfiguration() {
>>>>>>> > +        return configuration;
>>>>>>> > +    }
>>>>>>> > }
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..7d90f6b
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>> > @@ -0,0 +1,130 @@
>>>>>>> > +/*
>>>>>>> > + * 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 <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.appender.routing;
>>>>>>> > +
>>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>>> > +
>>>>>>> > +import java.util.List;
>>>>>>> > +import java.util.Map;
>>>>>>> > +
>>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>>> > +import org.junit.Assert;
>>>>>>> > +import org.junit.Rule;
>>>>>>> > +import org.junit.Test;
>>>>>>> > +import org.junit.runner.RunWith;
>>>>>>> > +import org.junit.runners.Parameterized;
>>>>>>> > +
>>>>>>> > +/**
>>>>>>> > + *
>>>>>>> > + */
>>>>>>> > +@RunWith(Parameterized.class)
>>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>>> > +
>>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>>> > +    public static String[] getParameters() {
>>>>>>> > +        return new String[] {
>>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Rule
>>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>>> > +
>>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private ListAppender getListAppender() {
>>>>>>> > +        final String key = "Service2";
>>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>>>>> > +        return listAppender;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private void logAndCheck() {
>>>>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>>>>> > +        logger.error("Hello");
>>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>>> > +        assertNotNull("No events generated", list);
>>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>>>>> > +        logger.error("World");
>>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>>> > +    public void testAppenderAbsence() {
>>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testListAppenderPresence() {
>>>>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testNoPurgePolicy() {
>>>>>>> > +        // No PurgePolicy in this test
>>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testNoRewritePolicy() {
>>>>>>> > +        // No RewritePolicy in this test
>>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>>> > +        Assert.assertNotNull(routes);
>>>>>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>>> > +        getRoutingAppender();
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingPresence1() {
>>>>>>> > +        logAndCheck();
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingPresence2() {
>>>>>>> > +        logAndCheck();
>>>>>>> > +    }
>>>>>>> > +}
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..83121ea
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>>>>>> > +  <Appenders>
>>>>>>> > +    <Routing name="Routing">
>>>>>>> > +      <Routes>
>>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>>> > +            return "Service2";
>>>>>>> > +          }
>>>>>>> > +          return "Service1";]]>
>>>>>>> > +        </Script>
>>>>>>> > +        <Route key="Service1">
>>>>>>> > +          <List name="List1" />
>>>>>>> > +        </Route>
>>>>>>> > +        <Route key="Service2">
>>>>>>> > +          <List name="List2" />
>>>>>>> > +        </Route>
>>>>>>> > +      </Routes>
>>>>>>> > +    </Routing>
>>>>>>> > +  </Appenders>
>>>>>>> > +  <Loggers>
>>>>>>> > +    <Root level="error">
>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>> > +    </Root>
>>>>>>> > +  </Loggers>
>>>>>>> > +</Configuration>
>>>>>>> > \ No newline at end of file
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..e672aea
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>>> > @@ -0,0 +1,40 @@
>>>>>>> > +<?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 <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="WARN" name="RoutingTest">
>>>>>>> > +  <Appenders>
>>>>>>> > +    <Routing name="Routing">
>>>>>>> > +      <Routes>
>>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>>>>> > +        </Script>
>>>>>>> > +        <Route key="Service1">
>>>>>>> > +          <List name="List1" />
>>>>>>> > +        </Route>
>>>>>>> > +        <Route key="Service2">
>>>>>>> > +          <List name="List2" />
>>>>>>> > +        </Route>
>>>>>>> > +      </Routes>
>>>>>>> > +    </Routing>
>>>>>>> > +  </Appenders>
>>>>>>> > +  <Loggers>
>>>>>>> > +    <Root level="error">
>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>> > +    </Root>
>>>>>>> > +  </Loggers>
>>>>>>> > +</Configuration>
>>>>>>> > \ No newline at end of file
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>>>>>> > ----------------------------------------------------------------------
>>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>>>>> >              Appenders it references to allow it to shut down properly.
>>>>>>> > -          </p>
>>>>>>> > +           </p>
>>>>>>> > +           <p>
>>>>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>>>>> > +             and when a route is chosen for an log event.
>>>>>>> > +           </p>
>>>>>>> >           <table>
>>>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>>>> >             <tr>
>>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>>> >               <th>Description</th>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>filter</td>
>>>>>>> > +              <td>Filter</td>
>>>>>>> >               <td>Filter</td>
>>>>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>>> >               <td>The name of the Appender.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>rewritePolicy</td>
>>>>>>> > +              <td>RewritePolicy</td>
>>>>>>> >               <td>RewritePolicy</td>
>>>>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>routes</td>
>>>>>>> > +              <td>Routes</td>
>>>>>>> >               <td>Routes</td>
>>>>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > +              <td>Script</td>
>>>>>>> > +              <td>Script</td>
>>>>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>>>>> > +                the default Route.</td>
>>>>>>> > +            </tr>
>>>>>>> > +            <tr>
>>>>>>> >               <td>ignoreExceptions</td>
>>>>>>> >               <td>boolean</td>
>>>>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>>> >           </table>
>>>>>>> >           <h4>Routes</h4>
>>>>>>> >             <p>
>>>>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>>>>> >               can be configured as the default.
>>>>>>> >             </p>
>>>>>>> >             <p>
>>>>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>>>>> > +              log event and returns the String Route key to use.
>>>>>>> > +            </p>
>>>>>>> > +            <p>
>>>>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>>>>> > +            </p>
>>>>>>> > +            <p>
>>>>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>>>>> >
>>>>>>> >
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> ---------------------------------------------------------------------
>>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> -- 
>>>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> -- 
>>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>>> 
>>>>> 
>>>>> 
>>>>> -- 
>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>> 
> 
> 
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
FYI: I just pushed site docs updates.

Gary

On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <ga...@gmail.com>
wrote:

> And I am not worried about concurrency around the log event.
>
> Gary
>
> On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <ga...@gmail.com>
> wrote:
>
>> OK, I've just reworked the code. Bindings are no longer shared, only the
>> ConcurrentMap is shared. Please review.
>>
>> Gary
>>
>> On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <ra...@dslextreme.com>
>> wrote:
>>
>>> And obviously the Bindings variable should not be a class member.
>>>
>>> Ralph
>>>
>>> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ra...@dslextreme.com>
>>> wrote:
>>>
>>> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>>>
>>> Ralph
>>>
>>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com>
>>> wrote:
>>>
>>> That's not how I have now. I'll revisit...
>>>
>>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ra...@dslextreme.com>
>>> wrote:
>>>
>>>> A new Binding is created for every script execution so thread safety is
>>>> not a problem.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com> wrote:
>>>>
>>>> If the Binding is shared between threads (and therefore not
>>>> thread-safe), you could put the LogEvent in a ThreadLocal.
>>>>
>>>> Sent from my iPhone
>>>>
>>>> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
>>>>
>>>> Ralph: Thank you for the guidance on this topic.
>>>>
>>>> I'll tackle the documentation update tonight.
>>>>
>>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on
>>>> that, I can take a look.
>>>>
>>>> I am wondering in general about the performance difference from an app
>>>> POV between a plain appender and one appender nested in a RoutingAppender.
>>>>
>>>> I am also wondering about any concurrency issue passing a LogEvent to a
>>>> Script. Can a LogEvent be skipped when multiple threads use the same
>>>> RoutingAppender?
>>>>
>>>> Gary
>>>>
>>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <
>>>> ralph.goers@dslextreme.com> wrote:
>>>>
>>>>> Sounds good.
>>>>>
>>>>> When documenting that you should make it clear that the Map is shared
>>>>> so that every thread is seeing the same Map. Users need to be aware that
>>>>> they cannot put things in the map and expect it to only be available for
>>>>> that single event.
>>>>>
>>>>> The last, and probably trickiest part, is going to be making it so
>>>>> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
>>>>> really figured out how to do that.
>>>>>
>>>>> Ralph
>>>>>
>>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com>
>>>>> wrote:
>>>>>
>>>>> The RoutingAppender Scripts now share a Bindings instance which
>>>>> contains a ConcurrentMap keyed under "staticVariables". The Bindings
>>>>> instance is tracked as a RoutingAppender and Routes ivar.
>>>>>
>>>>> I created an abstract superclass for (private) ScriptRunner
>>>>> implementations which holds on to the ConcurrentMap. The map can act as a
>>>>> set of static/global variables for that script and can be shared through a
>>>>> Bindings instance. The private ScriptRunner has new method
>>>>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>>>>> script specific data added to the Bindings, but there could be in the
>>>>> future.
>>>>>
>>>>> I'll add LogEvent support next...
>>>>>
>>>>> Gary
>>>>>
>>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <
>>>>> ralph.goers@dslextreme.com> wrote:
>>>>>
>>>>>> OK - It wasn’t in there when I looked last night.
>>>>>>
>>>>>> A couple other things. A ConcurrentMap should be created and passed
>>>>>> to the init script and the routing script so that the init script can pass
>>>>>> variables to the routing script. Also, the routing script really needs to
>>>>>> be passed the logEvent so it can route based on data within it.
>>>>>>
>>>>>> Ralph
>>>>>>
>>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>>>>>> wrote:
>>>>>>
>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <
>>>>>> ralph.goers@dslextreme.com> wrote:
>>>>>>
>>>>>>> Gary,
>>>>>>>
>>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense
>>>>>>> for the Routes class to execute the script in the call to getPattern and
>>>>>>> return the result if there is a script?
>>>>>>>
>>>>>>
>>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>>
>>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>       * @return the pattern.
>>>>>>       */
>>>>>>      public String getPattern() {
>>>>>> +        if (patternScript != null) {
>>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>>> +            bindings.put("configuration", configuration);
>>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>>> +            final Object object = configuration.getScriptManager
>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>> +            return Objects.toString(object, null);
>>>>>> +        }
>>>>>>          return pattern;
>>>>>>      }
>>>>>>
>>>>>> Gary
>>>>>>
>>>>>>
>>>>>>>
>>>>>>> Ralph
>>>>>>>
>>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>>>>> >
>>>>>>> > Repository: logging-log4j2
>>>>>>> > Updated Branches:
>>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>>> >
>>>>>>> >
>>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>>>>> Script
>>>>>>> > in a Routes element.
>>>>>>> >
>>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4
>>>>>>> j2/repo
>>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>>>>> 2/commit/e0f29d9a
>>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>> tree/e0f29d9a
>>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>>> diff/e0f29d9a
>>>>>>> >
>>>>>>> > Branch: refs/heads/master
>>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>>> > Parents: 3846e2a
>>>>>>> > Author: Gary Gregory <gg...@apache.org>
>>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>>> >
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>>>>> ++++++++++++-----
>>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>>>>> +++++++++++++++++++
>>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> >
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>> core/appender/routing/Routes.java
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/Routes.java
>>>>>>> > index c95b64a..33fccd7 100644
>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/Routes.java
>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/Routes.java
>>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>>>>> appender.routing;
>>>>>>> >
>>>>>>> > import java.util.Objects;
>>>>>>> >
>>>>>>> > +import javax.script.SimpleBindings;
>>>>>>> > +
>>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>>> > 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.PluginBuilderFactory;
>>>>>>> > +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.validation.constraints.Required;
>>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>>> >
>>>>>>> > /**
>>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>>> >
>>>>>>> >     public static class Builder implements
>>>>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>>> >
>>>>>>> > +        @PluginConfiguration
>>>>>>> > +        private Configuration configuration;
>>>>>>> > +
>>>>>>> >         @PluginAttribute("pattern")
>>>>>>> >         private String pattern;
>>>>>>> >
>>>>>>> > -        @PluginElement("Routes")
>>>>>>> > +        @PluginElement("Script")
>>>>>>> > +        private AbstractScript patternScript;
>>>>>>> > +
>>>>>>> > +        @PluginElement("Routes")
>>>>>>> > +        @Required
>>>>>>> >         private Route[] routes;
>>>>>>> >
>>>>>>> >         @Override
>>>>>>> >         public Routes build() {
>>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>>> > -                LOGGER.error("No routes configured");
>>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> > -            return new Routes(pattern, routes);
>>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>>> > +                LOGGER.warn("In a Routes element, you must
>>>>>>> configure either a Script element or a pattern attribute.");
>>>>>>> > +            }
>>>>>>> > +            if (patternScript != null) {
>>>>>>> > +                if (configuration == null) {
>>>>>>> > +                    LOGGER.error("No Configuration defined for
>>>>>>> Routes; required for Script");
>>>>>>> > +                } else {
>>>>>>> > +                    configuration.getScriptManager
>>>>>>> ().addScript(patternScript);
>>>>>>> > +                }
>>>>>>> > +            }
>>>>>>> > +            return new Routes(configuration, patternScript,
>>>>>>> pattern, routes);
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Configuration getConfiguration() {
>>>>>>> > +            return configuration;
>>>>>>> >         }
>>>>>>> >
>>>>>>> >         public String getPattern() {
>>>>>>> >             return pattern;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>>> > +            return patternScript;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> >         public Route[] getRoutes() {
>>>>>>> >             return routes;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>> String pattern) {
>>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>>>>> final Configuration configuration) {
>>>>>>> > +            this.configuration = configuration;
>>>>>>> > +            return this;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>>> final String pattern) {
>>>>>>> >             this.pattern = pattern;
>>>>>>> >             return this;
>>>>>>> >         }
>>>>>>> >
>>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>> Route[] routes) {
>>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>>>>> final AbstractScript patternScript) {
>>>>>>> > +            this.patternScript = patternScript;
>>>>>>> > +            return this;
>>>>>>> > +        }
>>>>>>> > +
>>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>>> final Route[] routes) {
>>>>>>> >             this.routes = routes;
>>>>>>> >             return this;
>>>>>>> >         }
>>>>>>> >
>>>>>>> >     }
>>>>>>> >
>>>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>>> > +
>>>>>>> > +    /**
>>>>>>> > +     * Creates the Routes.
>>>>>>> > +     * @param pattern The pattern.
>>>>>>> > +     * @param routes An array of Route elements.
>>>>>>> > +     * @return The Routes container.
>>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>> > +     */
>>>>>>> > +    @Deprecated
>>>>>>> > +    public static Routes createRoutes(
>>>>>>> > +            final String pattern,
>>>>>>> > +            final Route... routes) {
>>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>>> > +            LOGGER.error("No routes configured");
>>>>>>> > +            return null;
>>>>>>> > +        }
>>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> >     @PluginBuilderFactory
>>>>>>> >     public static Builder newBuilder() {
>>>>>>> >         return new Builder();
>>>>>>> >     }
>>>>>>> > -
>>>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>>> > -
>>>>>>> > +
>>>>>>> > +    private final Configuration configuration;
>>>>>>> > +
>>>>>>> >     private final String pattern;
>>>>>>> >
>>>>>>> > +    private final AbstractScript patternScript;
>>>>>>> > +
>>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>>> >     private final Route[] routes;
>>>>>>> >
>>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>>> > +    private Routes(final Configuration configuration, final
>>>>>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>>> > +        this.configuration = configuration;
>>>>>>> > +        this.patternScript = patternScript;
>>>>>>> >         this.pattern = pattern;
>>>>>>> >         this.routes = routes;
>>>>>>> >     }
>>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>> >      * @return the pattern.
>>>>>>> >      */
>>>>>>> >     public String getPattern() {
>>>>>>> > +        if (patternScript != null) {
>>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>>> > +            bindings.put("configuration", configuration);
>>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>>> > +            final Object object = configuration.getScriptManager
>>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>>> > +            return Objects.toString(object, null);
>>>>>>> > +        }
>>>>>>> >         return pattern;
>>>>>>> >     }
>>>>>>> >
>>>>>>> > -    public Route getRoute(String key) {
>>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>>> > -            final Route route = routes[i];
>>>>>>> > +    /**
>>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>>> > +     * @return the optional script that decides which route to
>>>>>>> pick. May be null.
>>>>>>> > +     */
>>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>>> > +        return patternScript;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    public Route getRoute(final String key) {
>>>>>>> > +        for (final Route route : routes) {
>>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>>> >                 return route;
>>>>>>> >             }
>>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>>> >
>>>>>>> >     }
>>>>>>> >
>>>>>>> > -    /**
>>>>>>> > -     * Creates the Routes.
>>>>>>> > -     * @param pattern The pattern.
>>>>>>> > -     * @param routes An array of Route elements.
>>>>>>> > -     * @return The Routes container.
>>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>>> > -     */
>>>>>>> > -    @Deprecated
>>>>>>> > -    public static Routes createRoutes(
>>>>>>> > -            final String pattern,
>>>>>>> > -            final Route... routes) {
>>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>>> > -            LOGGER.error("No routes configured");
>>>>>>> > -            return null;
>>>>>>> > -        }
>>>>>>> > -        return new Routes(pattern, routes);
>>>>>>> > -    }
>>>>>>> > -
>>>>>>> > }
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>>> core/appender/routing/RoutingAppender.java
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/RoutingAppender.java
>>>>>>> > index 4471333..78fddbc 100644
>>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/RoutingAppender.java
>>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/RoutingAppender.java
>>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>>>>> AbstractAppender {
>>>>>>> >
>>>>>>> >         @Override
>>>>>>> >         public RoutingAppender build() {
>>>>>>> > -            if (getName() == null) {
>>>>>>> > -                LOGGER.error("No name defined for
>>>>>>> RoutingAppender");
>>>>>>> > +            final String name = getName();
>>>>>>> > +            if (name == null) {
>>>>>>> > +                LOGGER.error("No name defined for this
>>>>>>> RoutingAppender");
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> >             if (routes == null) {
>>>>>>> > -                LOGGER.error("No routes defined for
>>>>>>> RoutingAppender");
>>>>>>> > +                LOGGER.error("No routes defined for
>>>>>>> RoutingAppender {}", name);
>>>>>>> >                 return null;
>>>>>>> >             }
>>>>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>> > +            return new RoutingAppender(name, getFilter(),
>>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>>> >                     configuration, purgePolicy,
>>>>>>> defaultRouteScript);
>>>>>>> >         }
>>>>>>> >
>>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>>>>> AbstractAppender {
>>>>>>> >     public void start() {
>>>>>>> >         if (defaultRouteScript != null) {
>>>>>>> >             if (configuration == null) {
>>>>>>> > -                error("No Configuration defined for
>>>>>>> RoutingAppender; required for DefaultRouteScript");
>>>>>>> > +                error("No Configuration defined for
>>>>>>> RoutingAppender; required for Script element.");
>>>>>>> >             } else {
>>>>>>> >                 configuration.getScriptManage
>>>>>>> r().addScript(defaultRouteScript);
>>>>>>> >                 final SimpleBindings bindings = new
>>>>>>> SimpleBindings();
>>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>>>>> AbstractAppender {
>>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>>> >         return rewritePolicy;
>>>>>>> >     }
>>>>>>> > +
>>>>>>> > +    public Routes getRoutes() {
>>>>>>> > +        return routes;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    public Configuration getConfiguration() {
>>>>>>> > +        return configuration;
>>>>>>> > +    }
>>>>>>> > }
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/log4j-core/src/test/java/org
>>>>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..7d90f6b
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>>> > @@ -0,0 +1,130 @@
>>>>>>> > +/*
>>>>>>> > + * 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.appender.routing;
>>>>>>> > +
>>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>>> > +
>>>>>>> > +import java.util.List;
>>>>>>> > +import java.util.Map;
>>>>>>> > +
>>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>>> > +import org.junit.Assert;
>>>>>>> > +import org.junit.Rule;
>>>>>>> > +import org.junit.Test;
>>>>>>> > +import org.junit.runner.RunWith;
>>>>>>> > +import org.junit.runners.Parameterized;
>>>>>>> > +
>>>>>>> > +/**
>>>>>>> > + *
>>>>>>> > + */
>>>>>>> > +@RunWith(Parameterized.class)
>>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>>> > +
>>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>>> > +    public static String[] getParameters() {
>>>>>>> > +        return new String[] {
>>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Rule
>>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>>> > +
>>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>>>> > +        this.loggerContextRule = new
>>>>>>> LoggerContextRule(configLocation);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private ListAppender getListAppender() {
>>>>>>> > +        final String key = "Service2";
>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>> getRoutingAppender();
>>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>>> > +        final Map<String, AppenderControl> appenders =
>>>>>>> routingAppender.getAppenders();
>>>>>>> > +        final AppenderControl appenderControl =
>>>>>>> appenders.get(key);
>>>>>>> > +        assertNotNull("No appender control generated for '" + key
>>>>>>> + "'; appenders = " + appenders, appenderControl);
>>>>>>> > +        final ListAppender listAppender = (ListAppender)
>>>>>>> appenderControl.getAppender();
>>>>>>> > +        return listAppender;
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>>>>> RoutingAppender.class);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    private void logAndCheck() {
>>>>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>>>>> utesScriptAppenderTest.class);
>>>>>>> > +        logger.error("Hello");
>>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>>> > +        assertNotNull("No events generated", list);
>>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got "
>>>>>>> + list.size(), list.size() == 1);
>>>>>>> > +        logger.error("World");
>>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got "
>>>>>>> + list.size(), list.size() == 2);
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>>> > +    public void testAppenderAbsence() {
>>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testListAppenderPresence() {
>>>>>>> > +        // No appender until an event is routed, even thought we
>>>>>>> initialized the default route on startup.
>>>>>>> > +        Assert.assertNull("No appender control generated",
>>>>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testNoPurgePolicy() {
>>>>>>> > +        // No PurgePolicy in this test
>>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>>>>> getRoutingAppender().getPurgePolicy());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testNoRewritePolicy() {
>>>>>>> > +        // No RewritePolicy in this test
>>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>>>>> getRoutingAppender().getRewritePolicy());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>>> > +        final RoutingAppender routingAppender =
>>>>>>> getRoutingAppender();
>>>>>>> > +        Assert.assertNull(routingAppen
>>>>>>> der.getDefaultRouteScript());
>>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>>> > +        Assert.assertNotNull(routes);
>>>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>>> > +        getRoutingAppender();
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingPresence1() {
>>>>>>> > +        logAndCheck();
>>>>>>> > +    }
>>>>>>> > +
>>>>>>> > +    @Test
>>>>>>> > +    public void testRoutingPresence2() {
>>>>>>> > +        logAndCheck();
>>>>>>> > +    }
>>>>>>> > +}
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>> -script-groovy.xml
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>> s/log4j-routing-routes-script-groovy.xml
>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>> groovy.xml
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..83121ea
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>> groovy.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="WARN" name="RoutingTest">
>>>>>>> > +  <Appenders>
>>>>>>> > +    <Routing name="Routing">
>>>>>>> > +      <Routes>
>>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>>> > +            return "Service2";
>>>>>>> > +          }
>>>>>>> > +          return "Service1";]]>
>>>>>>> > +        </Script>
>>>>>>> > +        <Route key="Service1">
>>>>>>> > +          <List name="List1" />
>>>>>>> > +        </Route>
>>>>>>> > +        <Route key="Service2">
>>>>>>> > +          <List name="List2" />
>>>>>>> > +        </Route>
>>>>>>> > +      </Routes>
>>>>>>> > +    </Routing>
>>>>>>> > +  </Appenders>
>>>>>>> > +  <Loggers>
>>>>>>> > +    <Root level="error">
>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>> > +    </Root>
>>>>>>> > +  </Loggers>
>>>>>>> > +</Configuration>
>>>>>>> > \ No newline at end of file
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>>> -script-javascript.xml
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>>> s/log4j-routing-routes-script-javascript.xml
>>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>> javascript.xml
>>>>>>> > new file mode 100644
>>>>>>> > index 0000000..e672aea
>>>>>>> > --- /dev/null
>>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>>> javascript.xml
>>>>>>> > @@ -0,0 +1,40 @@
>>>>>>> > +<?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="WARN" name="RoutingTest">
>>>>>>> > +  <Appenders>
>>>>>>> > +    <Routing name="Routing">
>>>>>>> > +      <Routes>
>>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA
>>>>>>> [
>>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>>>>> "Service1";]]>
>>>>>>> > +        </Script>
>>>>>>> > +        <Route key="Service1">
>>>>>>> > +          <List name="List1" />
>>>>>>> > +        </Route>
>>>>>>> > +        <Route key="Service2">
>>>>>>> > +          <List name="List2" />
>>>>>>> > +        </Route>
>>>>>>> > +      </Routes>
>>>>>>> > +    </Routing>
>>>>>>> > +  </Appenders>
>>>>>>> > +  <Loggers>
>>>>>>> > +    <Root level="error">
>>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>>> > +    </Root>
>>>>>>> > +  </Loggers>
>>>>>>> > +</Configuration>
>>>>>>> > \ No newline at end of file
>>>>>>> >
>>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>>>> > ------------------------------------------------------------
>>>>>>> ----------
>>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>>>>> b/src/site/xdoc/manual/appenders.xml
>>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>> >              Appender may be an appender previously configured and
>>>>>>> may be referenced by its name or the
>>>>>>> >              Appender can be dynamically created as needed. The
>>>>>>> RoutingAppender should be configured after any
>>>>>>> >              Appenders it references to allow it to shut down
>>>>>>> properly.
>>>>>>> > -          </p>
>>>>>>> > +           </p>
>>>>>>> > +           <p>
>>>>>>> > +             You can also configure a RoutingAppender with
>>>>>>> scripts: you can run a script when the appender starts
>>>>>>> > +             and when a route is chosen for an log event.
>>>>>>> > +           </p>
>>>>>>> >           <table>
>>>>>>> >             <caption align="top">RoutingAppender
>>>>>>> Parameters</caption>
>>>>>>> >             <tr>
>>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>> >               <th>Description</th>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>filter</td>
>>>>>>> > +              <td>Filter</td>
>>>>>>> >               <td>Filter</td>
>>>>>>> >               <td>A Filter to determine if the event should be
>>>>>>> handled by this Appender. More than one Filter
>>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>> >               <td>The name of the Appender.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>rewritePolicy</td>
>>>>>>> > +              <td>RewritePolicy</td>
>>>>>>> >               <td>RewritePolicy</td>
>>>>>>> >               <td>The RewritePolicy that will manipulate the
>>>>>>> LogEvent.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > -              <td>routes</td>
>>>>>>> > +              <td>Routes</td>
>>>>>>> >               <td>Routes</td>
>>>>>>> >               <td>Contains one or more Route declarations to
>>>>>>> identify the criteria for choosing Appenders.</td>
>>>>>>> >             </tr>
>>>>>>> >             <tr>
>>>>>>> > +              <td>Script</td>
>>>>>>> > +              <td>Script</td>
>>>>>>> > +              <td>This Script runs when Log4j starts the
>>>>>>> RoutingAppender and returns a String Route key to determine
>>>>>>> > +                the default Route.</td>
>>>>>>> > +            </tr>
>>>>>>> > +            <tr>
>>>>>>> >               <td>ignoreExceptions</td>
>>>>>>> >               <td>boolean</td>
>>>>>>> >               <td>The default is <code>true</code>, causing
>>>>>>> exceptions encountered while appending events to be
>>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>>>>> AbstractLogEventWrapperEntity {
>>>>>>> >           </table>
>>>>>>> >           <h4>Routes</h4>
>>>>>>> >             <p>
>>>>>>> > -              The Routes element accepts a single, required
>>>>>>> attribute named "pattern". The pattern is evaluated
>>>>>>> > +              The Routes element accepts a single attribute named
>>>>>>> "pattern". The pattern is evaluated
>>>>>>> >               against all the registered Lookups and the result is
>>>>>>> used to select a Route. Each Route may be
>>>>>>> >               configured with a key. If the key matches the result
>>>>>>> of evaluating the pattern then that Route
>>>>>>> >               will be selected. If no key is specified on a Route
>>>>>>> then that Route is the default. Only one Route
>>>>>>> >               can be configured as the default.
>>>>>>> >             </p>
>>>>>>> >             <p>
>>>>>>> > +              The Routes element may contain a Script child
>>>>>>> element. If specified, the Script is run for each
>>>>>>> > +              log event and returns the String Route key to use.
>>>>>>> > +            </p>
>>>>>>> > +            <p>
>>>>>>> > +              You must specify either the pattern attribute or
>>>>>>> the Script element, but not both.
>>>>>>> > +            </p>
>>>>>>> > +            <p>
>>>>>>> >               Each Route must reference an Appender. If the Route
>>>>>>> contains a ref attribute then the
>>>>>>> >               Route will reference an Appender that was defined in
>>>>>>> the configuration. If the Route contains an
>>>>>>> >               Appender definition then an Appender will be created
>>>>>>> within the context of the RoutingAppender and
>>>>>>> >
>>>>>>> >
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> ------------------------------------------------------------
>>>>>>> ---------
>>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>>> <gg...@apache.org>
>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>> <http://www.manning.com/bauer3/>
>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>> Blog: http://garygregory.wordpress.com
>>>>>> Home: http://garygregory.com/
>>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>> Java Persistence with Hibernate, Second Edition
>>>>> <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com
>>>>> Home: http://garygregory.com/
>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>> Java Persistence with Hibernate, Second Edition
>>>> <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com
>>>> Home: http://garygregory.com/
>>>> Tweet! http://twitter.com/GaryGregory
>>>>
>>>>
>>>>
>>>
>>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>



-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
And I am not worried about concurrency around the log event.

Gary

On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <ga...@gmail.com>
wrote:

> OK, I've just reworked the code. Bindings are no longer shared, only the
> ConcurrentMap is shared. Please review.
>
> Gary
>
> On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <ra...@dslextreme.com>
> wrote:
>
>> And obviously the Bindings variable should not be a class member.
>>
>> Ralph
>>
>> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ra...@dslextreme.com>
>> wrote:
>>
>> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>>
>> Ralph
>>
>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com> wrote:
>>
>> That's not how I have now. I'll revisit...
>>
>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ra...@dslextreme.com>
>> wrote:
>>
>>> A new Binding is created for every script execution so thread safety is
>>> not a problem.
>>>
>>> Ralph
>>>
>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com> wrote:
>>>
>>> If the Binding is shared between threads (and therefore not
>>> thread-safe), you could put the LogEvent in a ThreadLocal.
>>>
>>> Sent from my iPhone
>>>
>>> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
>>>
>>> Ralph: Thank you for the guidance on this topic.
>>>
>>> I'll tackle the documentation update tonight.
>>>
>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on
>>> that, I can take a look.
>>>
>>> I am wondering in general about the performance difference from an app
>>> POV between a plain appender and one appender nested in a RoutingAppender.
>>>
>>> I am also wondering about any concurrency issue passing a LogEvent to a
>>> Script. Can a LogEvent be skipped when multiple threads use the same
>>> RoutingAppender?
>>>
>>> Gary
>>>
>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <
>>> ralph.goers@dslextreme.com> wrote:
>>>
>>>> Sounds good.
>>>>
>>>> When documenting that you should make it clear that the Map is shared
>>>> so that every thread is seeing the same Map. Users need to be aware that
>>>> they cannot put things in the map and expect it to only be available for
>>>> that single event.
>>>>
>>>> The last, and probably trickiest part, is going to be making it so
>>>> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
>>>> really figured out how to do that.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com>
>>>> wrote:
>>>>
>>>> The RoutingAppender Scripts now share a Bindings instance which
>>>> contains a ConcurrentMap keyed under "staticVariables". The Bindings
>>>> instance is tracked as a RoutingAppender and Routes ivar.
>>>>
>>>> I created an abstract superclass for (private) ScriptRunner
>>>> implementations which holds on to the ConcurrentMap. The map can act as a
>>>> set of static/global variables for that script and can be shared through a
>>>> Bindings instance. The private ScriptRunner has new method
>>>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>>>> script specific data added to the Bindings, but there could be in the
>>>> future.
>>>>
>>>> I'll add LogEvent support next...
>>>>
>>>> Gary
>>>>
>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <
>>>> ralph.goers@dslextreme.com> wrote:
>>>>
>>>>> OK - It wasn’t in there when I looked last night.
>>>>>
>>>>> A couple other things. A ConcurrentMap should be created and passed to
>>>>> the init script and the routing script so that the init script can pass
>>>>> variables to the routing script. Also, the routing script really needs to
>>>>> be passed the logEvent so it can route based on data within it.
>>>>>
>>>>> Ralph
>>>>>
>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>>>>> wrote:
>>>>>
>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.
>>>>> com> wrote:
>>>>>
>>>>>> Gary,
>>>>>>
>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense
>>>>>> for the Routes class to execute the script in the call to getPattern and
>>>>>> return the result if there is a script?
>>>>>>
>>>>>
>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>
>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>       * @return the pattern.
>>>>>       */
>>>>>      public String getPattern() {
>>>>> +        if (patternScript != null) {
>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>> +            bindings.put("configuration", configuration);
>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>> +            final Object object = configuration.getScriptManager
>>>>> ().execute(patternScript.getName(), bindings);
>>>>> +            return Objects.toString(object, null);
>>>>> +        }
>>>>>          return pattern;
>>>>>      }
>>>>>
>>>>> Gary
>>>>>
>>>>>
>>>>>>
>>>>>> Ralph
>>>>>>
>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>>>> >
>>>>>> > Repository: logging-log4j2
>>>>>> > Updated Branches:
>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>> >
>>>>>> >
>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>>>> Script
>>>>>> > in a Routes element.
>>>>>> >
>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>>>> 2/commit/e0f29d9a
>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>> tree/e0f29d9a
>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>>> diff/e0f29d9a
>>>>>> >
>>>>>> > Branch: refs/heads/master
>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>> > Parents: 3846e2a
>>>>>> > Author: Gary Gregory <gg...@apache.org>
>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>> >
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>>>> ++++++++++++-----
>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>>>> +++++++++++++++++++
>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> >
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>> core/appender/routing/Routes.java
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/Routes.java
>>>>>> > index c95b64a..33fccd7 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/Routes.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/Routes.java
>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>>>> appender.routing;
>>>>>> >
>>>>>> > import java.util.Objects;
>>>>>> >
>>>>>> > +import javax.script.SimpleBindings;
>>>>>> > +
>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>> > 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.PluginBuilderFa
>>>>>> ctory;
>>>>>> > +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.validation.constraints.Required;
>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>> >
>>>>>> > /**
>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>> >
>>>>>> >     public static class Builder implements
>>>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>> >
>>>>>> > +        @PluginConfiguration
>>>>>> > +        private Configuration configuration;
>>>>>> > +
>>>>>> >         @PluginAttribute("pattern")
>>>>>> >         private String pattern;
>>>>>> >
>>>>>> > -        @PluginElement("Routes")
>>>>>> > +        @PluginElement("Script")
>>>>>> > +        private AbstractScript patternScript;
>>>>>> > +
>>>>>> > +        @PluginElement("Routes")
>>>>>> > +        @Required
>>>>>> >         private Route[] routes;
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public Routes build() {
>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>> > -                LOGGER.error("No routes configured");
>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new Routes(pattern, routes);
>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>> > +                LOGGER.warn("In a Routes element, you must
>>>>>> configure either a Script element or a pattern attribute.");
>>>>>> > +            }
>>>>>> > +            if (patternScript != null) {
>>>>>> > +                if (configuration == null) {
>>>>>> > +                    LOGGER.error("No Configuration defined for
>>>>>> Routes; required for Script");
>>>>>> > +                } else {
>>>>>> > +                    configuration.getScriptManager
>>>>>> ().addScript(patternScript);
>>>>>> > +                }
>>>>>> > +            }
>>>>>> > +            return new Routes(configuration, patternScript,
>>>>>> pattern, routes);
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Configuration getConfiguration() {
>>>>>> > +            return configuration;
>>>>>> >         }
>>>>>> >
>>>>>> >         public String getPattern() {
>>>>>> >             return pattern;
>>>>>> >         }
>>>>>> >
>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>> > +            return patternScript;
>>>>>> > +        }
>>>>>> > +
>>>>>> >         public Route[] getRoutes() {
>>>>>> >             return routes;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>> String pattern) {
>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>>>> final Configuration configuration) {
>>>>>> > +            this.configuration = configuration;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>>>> final String pattern) {
>>>>>> >             this.pattern = pattern;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>> Route[] routes) {
>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>>>> final AbstractScript patternScript) {
>>>>>> > +            this.patternScript = patternScript;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>>> final Route[] routes) {
>>>>>> >             this.routes = routes;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > +
>>>>>> > +    /**
>>>>>> > +     * Creates the Routes.
>>>>>> > +     * @param pattern The pattern.
>>>>>> > +     * @param routes An array of Route elements.
>>>>>> > +     * @return The Routes container.
>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > +     */
>>>>>> > +    @Deprecated
>>>>>> > +    public static Routes createRoutes(
>>>>>> > +            final String pattern,
>>>>>> > +            final Route... routes) {
>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>> > +            LOGGER.error("No routes configured");
>>>>>> > +            return null;
>>>>>> > +        }
>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>> > +    }
>>>>>> > +
>>>>>> >     @PluginBuilderFactory
>>>>>> >     public static Builder newBuilder() {
>>>>>> >         return new Builder();
>>>>>> >     }
>>>>>> > -
>>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > -
>>>>>> > +
>>>>>> > +    private final Configuration configuration;
>>>>>> > +
>>>>>> >     private final String pattern;
>>>>>> >
>>>>>> > +    private final AbstractScript patternScript;
>>>>>> > +
>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>> >     private final Route[] routes;
>>>>>> >
>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>> > +    private Routes(final Configuration configuration, final
>>>>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>> > +        this.configuration = configuration;
>>>>>> > +        this.patternScript = patternScript;
>>>>>> >         this.pattern = pattern;
>>>>>> >         this.routes = routes;
>>>>>> >     }
>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>> >      * @return the pattern.
>>>>>> >      */
>>>>>> >     public String getPattern() {
>>>>>> > +        if (patternScript != null) {
>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>> > +            bindings.put("configuration", configuration);
>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>> > +            final Object object = configuration.getScriptManager
>>>>>> ().execute(patternScript.getName(), bindings);
>>>>>> > +            return Objects.toString(object, null);
>>>>>> > +        }
>>>>>> >         return pattern;
>>>>>> >     }
>>>>>> >
>>>>>> > -    public Route getRoute(String key) {
>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>> > -            final Route route = routes[i];
>>>>>> > +    /**
>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>> > +     * @return the optional script that decides which route to
>>>>>> pick. May be null.
>>>>>> > +     */
>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>> > +        return patternScript;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Route getRoute(final String key) {
>>>>>> > +        for (final Route route : routes) {
>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>> >                 return route;
>>>>>> >             }
>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > -    /**
>>>>>> > -     * Creates the Routes.
>>>>>> > -     * @param pattern The pattern.
>>>>>> > -     * @param routes An array of Route elements.
>>>>>> > -     * @return The Routes container.
>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > -     */
>>>>>> > -    @Deprecated
>>>>>> > -    public static Routes createRoutes(
>>>>>> > -            final String pattern,
>>>>>> > -            final Route... routes) {
>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>> > -            LOGGER.error("No routes configured");
>>>>>> > -            return null;
>>>>>> > -        }
>>>>>> > -        return new Routes(pattern, routes);
>>>>>> > -    }
>>>>>> > -
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>>> core/appender/routing/RoutingAppender.java
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/RoutingAppender.java
>>>>>> > index 4471333..78fddbc 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/RoutingAppender.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/RoutingAppender.java
>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>>>> AbstractAppender {
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public RoutingAppender build() {
>>>>>> > -            if (getName() == null) {
>>>>>> > -                LOGGER.error("No name defined for
>>>>>> RoutingAppender");
>>>>>> > +            final String name = getName();
>>>>>> > +            if (name == null) {
>>>>>> > +                LOGGER.error("No name defined for this
>>>>>> RoutingAppender");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> >             if (routes == null) {
>>>>>> > -                LOGGER.error("No routes defined for
>>>>>> RoutingAppender");
>>>>>> > +                LOGGER.error("No routes defined for
>>>>>> RoutingAppender {}", name);
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> > +            return new RoutingAppender(name, getFilter(),
>>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>>> >         }
>>>>>> >
>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>>>> AbstractAppender {
>>>>>> >     public void start() {
>>>>>> >         if (defaultRouteScript != null) {
>>>>>> >             if (configuration == null) {
>>>>>> > -                error("No Configuration defined for
>>>>>> RoutingAppender; required for DefaultRouteScript");
>>>>>> > +                error("No Configuration defined for
>>>>>> RoutingAppender; required for Script element.");
>>>>>> >             } else {
>>>>>> >                 configuration.getScriptManage
>>>>>> r().addScript(defaultRouteScript);
>>>>>> >                 final SimpleBindings bindings = new
>>>>>> SimpleBindings();
>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>>>> AbstractAppender {
>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>> >         return rewritePolicy;
>>>>>> >     }
>>>>>> > +
>>>>>> > +    public Routes getRoutes() {
>>>>>> > +        return routes;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Configuration getConfiguration() {
>>>>>> > +        return configuration;
>>>>>> > +    }
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/log4j-core/src/test/java/org
>>>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>> > new file mode 100644
>>>>>> > index 0000000..7d90f6b
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>>> > @@ -0,0 +1,130 @@
>>>>>> > +/*
>>>>>> > + * 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.appender.routing;
>>>>>> > +
>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>> > +
>>>>>> > +import java.util.List;
>>>>>> > +import java.util.Map;
>>>>>> > +
>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>> > +import org.junit.Assert;
>>>>>> > +import org.junit.Rule;
>>>>>> > +import org.junit.Test;
>>>>>> > +import org.junit.runner.RunWith;
>>>>>> > +import org.junit.runners.Parameterized;
>>>>>> > +
>>>>>> > +/**
>>>>>> > + *
>>>>>> > + */
>>>>>> > +@RunWith(Parameterized.class)
>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>> > +
>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>> > +    public static String[] getParameters() {
>>>>>> > +        return new String[] {
>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Rule
>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>> > +
>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>>>>>> on);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private ListAppender getListAppender() {
>>>>>> > +        final String key = "Service2";
>>>>>> > +        final RoutingAppender routingAppender =
>>>>>> getRoutingAppender();
>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>> > +        final Map<String, AppenderControl> appenders =
>>>>>> routingAppender.getAppenders();
>>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>>> > +        assertNotNull("No appender control generated for '" + key
>>>>>> + "'; appenders = " + appenders, appenderControl);
>>>>>> > +        final ListAppender listAppender = (ListAppender)
>>>>>> appenderControl.getAppender();
>>>>>> > +        return listAppender;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>>>> RoutingAppender.class);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private void logAndCheck() {
>>>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>>>> utesScriptAppenderTest.class);
>>>>>> > +        logger.error("Hello");
>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>> > +        assertNotNull("No events generated", list);
>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got "
>>>>>> + list.size(), list.size() == 1);
>>>>>> > +        logger.error("World");
>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got "
>>>>>> + list.size(), list.size() == 2);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>> > +    public void testAppenderAbsence() {
>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testListAppenderPresence() {
>>>>>> > +        // No appender until an event is routed, even thought we
>>>>>> initialized the default route on startup.
>>>>>> > +        Assert.assertNull("No appender control generated",
>>>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoPurgePolicy() {
>>>>>> > +        // No PurgePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>>>> getRoutingAppender().getPurgePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoRewritePolicy() {
>>>>>> > +        // No RewritePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>>>> getRoutingAppender().getRewritePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>> > +        final RoutingAppender routingAppender =
>>>>>> getRoutingAppender();
>>>>>> > +        Assert.assertNull(routingAppen
>>>>>> der.getDefaultRouteScript());
>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>> > +        Assert.assertNotNull(routes);
>>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>> > +        getRoutingAppender();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence1() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence2() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +}
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>> -script-groovy.xml
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>> s/log4j-routing-routes-script-groovy.xml
>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>> groovy.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..83121ea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>> groovy.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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>> > +            return "Service2";
>>>>>> > +          }
>>>>>> > +          return "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>>> -script-javascript.xml
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/log4j-core/src/test/resource
>>>>>> s/log4j-routing-routes-script-javascript.xml
>>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>> javascript.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..e672aea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>>> javascript.xml
>>>>>> > @@ -0,0 +1,40 @@
>>>>>> > +<?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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>>>> "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>>> > ------------------------------------------------------------
>>>>>> ----------
>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>>>> b/src/site/xdoc/manual/appenders.xml
>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>>>> AbstractLogEventWrapperEntity {
>>>>>> >              Appender may be an appender previously configured and
>>>>>> may be referenced by its name or the
>>>>>> >              Appender can be dynamically created as needed. The
>>>>>> RoutingAppender should be configured after any
>>>>>> >              Appenders it references to allow it to shut down
>>>>>> properly.
>>>>>> > -          </p>
>>>>>> > +           </p>
>>>>>> > +           <p>
>>>>>> > +             You can also configure a RoutingAppender with
>>>>>> scripts: you can run a script when the appender starts
>>>>>> > +             and when a route is chosen for an log event.
>>>>>> > +           </p>
>>>>>> >           <table>
>>>>>> >             <caption align="top">RoutingAppender
>>>>>> Parameters</caption>
>>>>>> >             <tr>
>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>>>> AbstractLogEventWrapperEntity {
>>>>>> >               <th>Description</th>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>filter</td>
>>>>>> > +              <td>Filter</td>
>>>>>> >               <td>Filter</td>
>>>>>> >               <td>A Filter to determine if the event should be
>>>>>> handled by this Appender. More than one Filter
>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>>>> AbstractLogEventWrapperEntity {
>>>>>> >               <td>The name of the Appender.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>rewritePolicy</td>
>>>>>> > +              <td>RewritePolicy</td>
>>>>>> >               <td>RewritePolicy</td>
>>>>>> >               <td>The RewritePolicy that will manipulate the
>>>>>> LogEvent.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>routes</td>
>>>>>> > +              <td>Routes</td>
>>>>>> >               <td>Routes</td>
>>>>>> >               <td>Contains one or more Route declarations to
>>>>>> identify the criteria for choosing Appenders.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>This Script runs when Log4j starts the
>>>>>> RoutingAppender and returns a String Route key to determine
>>>>>> > +                the default Route.</td>
>>>>>> > +            </tr>
>>>>>> > +            <tr>
>>>>>> >               <td>ignoreExceptions</td>
>>>>>> >               <td>boolean</td>
>>>>>> >               <td>The default is <code>true</code>, causing
>>>>>> exceptions encountered while appending events to be
>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>>>> AbstractLogEventWrapperEntity {
>>>>>> >           </table>
>>>>>> >           <h4>Routes</h4>
>>>>>> >             <p>
>>>>>> > -              The Routes element accepts a single, required
>>>>>> attribute named "pattern". The pattern is evaluated
>>>>>> > +              The Routes element accepts a single attribute named
>>>>>> "pattern". The pattern is evaluated
>>>>>> >               against all the registered Lookups and the result is
>>>>>> used to select a Route. Each Route may be
>>>>>> >               configured with a key. If the key matches the result
>>>>>> of evaluating the pattern then that Route
>>>>>> >               will be selected. If no key is specified on a Route
>>>>>> then that Route is the default. Only one Route
>>>>>> >               can be configured as the default.
>>>>>> >             </p>
>>>>>> >             <p>
>>>>>> > +              The Routes element may contain a Script child
>>>>>> element. If specified, the Script is run for each
>>>>>> > +              log event and returns the String Route key to use.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> > +              You must specify either the pattern attribute or the
>>>>>> Script element, but not both.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> >               Each Route must reference an Appender. If the Route
>>>>>> contains a ref attribute then the
>>>>>> >               Route will reference an Appender that was defined in
>>>>>> the configuration. If the Route contains an
>>>>>> >               Appender definition then an Appender will be created
>>>>>> within the context of the RoutingAppender and
>>>>>> >
>>>>>> >
>>>>>>
>>>>>>
>>>>>>
>>>>>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>>> <gg...@apache.org>
>>>>> Java Persistence with Hibernate, Second Edition
>>>>> <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com
>>>>> Home: http://garygregory.com/
>>>>> Tweet! http://twitter.com/GaryGregory
>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>> Java Persistence with Hibernate, Second Edition
>>>> <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com
>>>> Home: http://garygregory.com/
>>>> Tweet! http://twitter.com/GaryGregory
>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>> Java Persistence with Hibernate, Second Edition
>>> <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
>>>
>>>
>>>
>>
>>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>



-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
OK, I've just reworked the code. Bindings are no longer shared, only the
ConcurrentMap is shared. Please review.

Gary

On Wed, Sep 14, 2016 at 4:38 PM, Ralph Goers <ra...@dslextreme.com>
wrote:

> And obviously the Bindings variable should not be a class member.
>
> Ralph
>
> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ra...@dslextreme.com>
> wrote:
>
> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
>
> Ralph
>
> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com> wrote:
>
> That's not how I have now. I'll revisit...
>
> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ra...@dslextreme.com> wrote:
>
>> A new Binding is created for every script execution so thread safety is
>> not a problem.
>>
>> Ralph
>>
>> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com> wrote:
>>
>> If the Binding is shared between threads (and therefore not thread-safe),
>> you could put the LogEvent in a ThreadLocal.
>>
>> Sent from my iPhone
>>
>> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
>>
>> Ralph: Thank you for the guidance on this topic.
>>
>> I'll tackle the documentation update tonight.
>>
>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on
>> that, I can take a look.
>>
>> I am wondering in general about the performance difference from an app
>> POV between a plain appender and one appender nested in a RoutingAppender.
>>
>> I am also wondering about any concurrency issue passing a LogEvent to a
>> Script. Can a LogEvent be skipped when multiple threads use the same
>> RoutingAppender?
>>
>> Gary
>>
>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com
>> > wrote:
>>
>>> Sounds good.
>>>
>>> When documenting that you should make it clear that the Map is shared so
>>> that every thread is seeing the same Map. Users need to be aware that they
>>> cannot put things in the map and expect it to only be available for that
>>> single event.
>>>
>>> The last, and probably trickiest part, is going to be making it so
>>> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
>>> really figured out how to do that.
>>>
>>> Ralph
>>>
>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com>
>>> wrote:
>>>
>>> The RoutingAppender Scripts now share a Bindings instance which contains
>>> a ConcurrentMap keyed under "staticVariables". The Bindings instance is
>>> tracked as a RoutingAppender and Routes ivar.
>>>
>>> I created an abstract superclass for (private) ScriptRunner
>>> implementations which holds on to the ConcurrentMap. The map can act as a
>>> set of static/global variables for that script and can be shared through a
>>> Bindings instance. The private ScriptRunner has new method
>>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>>> script specific data added to the Bindings, but there could be in the
>>> future.
>>>
>>> I'll add LogEvent support next...
>>>
>>> Gary
>>>
>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com
>>> > wrote:
>>>
>>>> OK - It wasn’t in there when I looked last night.
>>>>
>>>> A couple other things. A ConcurrentMap should be created and passed to
>>>> the init script and the routing script so that the init script can pass
>>>> variables to the routing script. Also, the routing script really needs to
>>>> be passed the logEvent so it can route based on data within it.
>>>>
>>>> Ralph
>>>>
>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>>>> wrote:
>>>>
>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.
>>>> com> wrote:
>>>>
>>>>> Gary,
>>>>>
>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for
>>>>> the Routes class to execute the script in the call to getPattern and return
>>>>> the result if there is a script?
>>>>>
>>>>
>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>
>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>       * @return the pattern.
>>>>       */
>>>>      public String getPattern() {
>>>> +        if (patternScript != null) {
>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>> +            bindings.put("configuration", configuration);
>>>> +            bindings.put("statusLogger", LOGGER);
>>>> +            final Object object = configuration.getScriptManager
>>>> ().execute(patternScript.getName(), bindings);
>>>> +            return Objects.toString(object, null);
>>>> +        }
>>>>          return pattern;
>>>>      }
>>>>
>>>> Gary
>>>>
>>>>
>>>>>
>>>>> Ralph
>>>>>
>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>>> >
>>>>> > Repository: logging-log4j2
>>>>> > Updated Branches:
>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>> >
>>>>> >
>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>>> Script
>>>>> > in a Routes element.
>>>>> >
>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>>> 2/commit/e0f29d9a
>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>> tree/e0f29d9a
>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>>> diff/e0f29d9a
>>>>> >
>>>>> > Branch: refs/heads/master
>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>> > Parents: 3846e2a
>>>>> > Author: Gary Gregory <gg...@apache.org>
>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>> >
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>>> ++++++++++++-----
>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>>> +++++++++++++++++++
>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> >
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>> core/appender/routing/Routes.java
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/Routes.java
>>>>> > index c95b64a..33fccd7 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/Routes.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/Routes.java
>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>>> appender.routing;
>>>>> >
>>>>> > import java.util.Objects;
>>>>> >
>>>>> > +import javax.script.SimpleBindings;
>>>>> > +
>>>>> > import org.apache.logging.log4j.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>> > 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.PluginBuilderFa
>>>>> ctory;
>>>>> > +import org.apache.logging.log4j.core.config.plugins.PluginConfigura
>>>>> tion;
>>>>> > import org.apache.logging.log4j.core.config.plugins.PluginElement;
>>>>> > +import org.apache.logging.log4j.core.config.plugins.validation.cons
>>>>> traints.Required;
>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>> >
>>>>> > /**
>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>> >
>>>>> >     public static class Builder implements
>>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>> >
>>>>> > +        @PluginConfiguration
>>>>> > +        private Configuration configuration;
>>>>> > +
>>>>> >         @PluginAttribute("pattern")
>>>>> >         private String pattern;
>>>>> >
>>>>> > -        @PluginElement("Routes")
>>>>> > +        @PluginElement("Script")
>>>>> > +        private AbstractScript patternScript;
>>>>> > +
>>>>> > +        @PluginElement("Routes")
>>>>> > +        @Required
>>>>> >         private Route[] routes;
>>>>> >
>>>>> >         @Override
>>>>> >         public Routes build() {
>>>>> >             if (routes == null || routes.length == 0) {
>>>>> > -                LOGGER.error("No routes configured");
>>>>> > +                LOGGER.error("No Routes configured.");
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new Routes(pattern, routes);
>>>>> > +            if (patternScript != null && pattern != null) {
>>>>> > +                LOGGER.warn("In a Routes element, you must
>>>>> configure either a Script element or a pattern attribute.");
>>>>> > +            }
>>>>> > +            if (patternScript != null) {
>>>>> > +                if (configuration == null) {
>>>>> > +                    LOGGER.error("No Configuration defined for
>>>>> Routes; required for Script");
>>>>> > +                } else {
>>>>> > +                    configuration.getScriptManager
>>>>> ().addScript(patternScript);
>>>>> > +                }
>>>>> > +            }
>>>>> > +            return new Routes(configuration, patternScript,
>>>>> pattern, routes);
>>>>> > +        }
>>>>> > +
>>>>> > +        public Configuration getConfiguration() {
>>>>> > +            return configuration;
>>>>> >         }
>>>>> >
>>>>> >         public String getPattern() {
>>>>> >             return pattern;
>>>>> >         }
>>>>> >
>>>>> > +        public AbstractScript getPatternScript() {
>>>>> > +            return patternScript;
>>>>> > +        }
>>>>> > +
>>>>> >         public Route[] getRoutes() {
>>>>> >             return routes;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>>> String pattern) {
>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>>> final Configuration configuration) {
>>>>> > +            this.configuration = configuration;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>>> final String pattern) {
>>>>> >             this.pattern = pattern;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>> Route[] routes) {
>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>>> final AbstractScript patternScript) {
>>>>> > +            this.patternScript = patternScript;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding")
>>>>> final Route[] routes) {
>>>>> >             this.routes = routes;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > +
>>>>> > +    /**
>>>>> > +     * Creates the Routes.
>>>>> > +     * @param pattern The pattern.
>>>>> > +     * @param routes An array of Route elements.
>>>>> > +     * @return The Routes container.
>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > +     */
>>>>> > +    @Deprecated
>>>>> > +    public static Routes createRoutes(
>>>>> > +            final String pattern,
>>>>> > +            final Route... routes) {
>>>>> > +        if (routes == null || routes.length == 0) {
>>>>> > +            LOGGER.error("No routes configured");
>>>>> > +            return null;
>>>>> > +        }
>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>> > +    }
>>>>> > +
>>>>> >     @PluginBuilderFactory
>>>>> >     public static Builder newBuilder() {
>>>>> >         return new Builder();
>>>>> >     }
>>>>> > -
>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > -
>>>>> > +
>>>>> > +    private final Configuration configuration;
>>>>> > +
>>>>> >     private final String pattern;
>>>>> >
>>>>> > +    private final AbstractScript patternScript;
>>>>> > +
>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>> >     private final Route[] routes;
>>>>> >
>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>> > +    private Routes(final Configuration configuration, final
>>>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>> > +        this.configuration = configuration;
>>>>> > +        this.patternScript = patternScript;
>>>>> >         this.pattern = pattern;
>>>>> >         this.routes = routes;
>>>>> >     }
>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>> >      * @return the pattern.
>>>>> >      */
>>>>> >     public String getPattern() {
>>>>> > +        if (patternScript != null) {
>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>> > +            bindings.put("configuration", configuration);
>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>> > +            final Object object = configuration.getScriptManager
>>>>> ().execute(patternScript.getName(), bindings);
>>>>> > +            return Objects.toString(object, null);
>>>>> > +        }
>>>>> >         return pattern;
>>>>> >     }
>>>>> >
>>>>> > -    public Route getRoute(String key) {
>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>> > -            final Route route = routes[i];
>>>>> > +    /**
>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>> > +     * @return the optional script that decides which route to
>>>>> pick. May be null.
>>>>> > +     */
>>>>> > +    public AbstractScript getPatternScript() {
>>>>> > +        return patternScript;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Route getRoute(final String key) {
>>>>> > +        for (final Route route : routes) {
>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>> >                 return route;
>>>>> >             }
>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > -    /**
>>>>> > -     * Creates the Routes.
>>>>> > -     * @param pattern The pattern.
>>>>> > -     * @param routes An array of Route elements.
>>>>> > -     * @return The Routes container.
>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > -     */
>>>>> > -    @Deprecated
>>>>> > -    public static Routes createRoutes(
>>>>> > -            final String pattern,
>>>>> > -            final Route... routes) {
>>>>> > -        if (routes == null || routes.length == 0) {
>>>>> > -            LOGGER.error("No routes configured");
>>>>> > -            return null;
>>>>> > -        }
>>>>> > -        return new Routes(pattern, routes);
>>>>> > -    }
>>>>> > -
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>>> core/appender/routing/RoutingAppender.java
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/log4j-core/src/main/java/org
>>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/RoutingAppender.java
>>>>> > index 4471333..78fddbc 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/RoutingAppender.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/RoutingAppender.java
>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>>> AbstractAppender {
>>>>> >
>>>>> >         @Override
>>>>> >         public RoutingAppender build() {
>>>>> > -            if (getName() == null) {
>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>> > +            final String name = getName();
>>>>> > +            if (name == null) {
>>>>> > +                LOGGER.error("No name defined for this
>>>>> RoutingAppender");
>>>>> >                 return null;
>>>>> >             }
>>>>> >             if (routes == null) {
>>>>> > -                LOGGER.error("No routes defined for
>>>>> RoutingAppender");
>>>>> > +                LOGGER.error("No routes defined for RoutingAppender
>>>>> {}", name);
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>> > +            return new RoutingAppender(name, getFilter(),
>>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>> >         }
>>>>> >
>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>>> AbstractAppender {
>>>>> >     public void start() {
>>>>> >         if (defaultRouteScript != null) {
>>>>> >             if (configuration == null) {
>>>>> > -                error("No Configuration defined for
>>>>> RoutingAppender; required for DefaultRouteScript");
>>>>> > +                error("No Configuration defined for
>>>>> RoutingAppender; required for Script element.");
>>>>> >             } else {
>>>>> >                 configuration.getScriptManage
>>>>> r().addScript(defaultRouteScript);
>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>>> AbstractAppender {
>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>> >         return rewritePolicy;
>>>>> >     }
>>>>> > +
>>>>> > +    public Routes getRoutes() {
>>>>> > +        return routes;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Configuration getConfiguration() {
>>>>> > +        return configuration;
>>>>> > +    }
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/log4j-core/src/test/java/org
>>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>> > new file mode 100644
>>>>> > index 0000000..7d90f6b
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>>> ender/routing/RoutesScriptAppenderTest.java
>>>>> > @@ -0,0 +1,130 @@
>>>>> > +/*
>>>>> > + * 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.appender.routing;
>>>>> > +
>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>> > +import static org.junit.Assert.assertTrue;
>>>>> > +
>>>>> > +import java.util.List;
>>>>> > +import java.util.Map;
>>>>> > +
>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>> > +import org.junit.Assert;
>>>>> > +import org.junit.Rule;
>>>>> > +import org.junit.Test;
>>>>> > +import org.junit.runner.RunWith;
>>>>> > +import org.junit.runners.Parameterized;
>>>>> > +
>>>>> > +/**
>>>>> > + *
>>>>> > + */
>>>>> > +@RunWith(Parameterized.class)
>>>>> > +public class RoutesScriptAppenderTest {
>>>>> > +
>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>> > +    public static String[] getParameters() {
>>>>> > +        return new String[] {
>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>> > +    }
>>>>> > +
>>>>> > +    @Rule
>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>> > +
>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>>>>> on);
>>>>> > +    }
>>>>> > +
>>>>> > +    private ListAppender getListAppender() {
>>>>> > +        final String key = "Service2";
>>>>> > +        final RoutingAppender routingAppender =
>>>>> getRoutingAppender();
>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>> > +        final Map<String, AppenderControl> appenders =
>>>>> routingAppender.getAppenders();
>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>> > +        assertNotNull("No appender control generated for '" + key +
>>>>> "'; appenders = " + appenders, appenderControl);
>>>>> > +        final ListAppender listAppender = (ListAppender)
>>>>> appenderControl.getAppender();
>>>>> > +        return listAppender;
>>>>> > +    }
>>>>> > +
>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>>> RoutingAppender.class);
>>>>> > +    }
>>>>> > +
>>>>> > +    private void logAndCheck() {
>>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>>> utesScriptAppenderTest.class);
>>>>> > +        logger.error("Hello");
>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>> > +        assertNotNull("No events generated", list);
>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " +
>>>>> list.size(), list.size() == 1);
>>>>> > +        logger.error("World");
>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " +
>>>>> list.size(), list.size() == 2);
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test(expected = AssertionError.class)
>>>>> > +    public void testAppenderAbsence() {
>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testListAppenderPresence() {
>>>>> > +        // No appender until an event is routed, even thought we
>>>>> initialized the default route on startup.
>>>>> > +        Assert.assertNull("No appender control generated",
>>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoPurgePolicy() {
>>>>> > +        // No PurgePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>>> getRoutingAppender().getPurgePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoRewritePolicy() {
>>>>> > +        // No RewritePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>>> getRoutingAppender().getRewritePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>> > +        final RoutingAppender routingAppender =
>>>>> getRoutingAppender();
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>> > +        Assert.assertNotNull(routes);
>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderPresence() {
>>>>> > +        getRoutingAppender();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence1() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence2() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +}
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>> -script-groovy.xml
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/log4j-core/src/test/resource
>>>>> s/log4j-routing-routes-script-groovy.xml
>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..83121ea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>> groovy.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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>> > +            return "Service2";
>>>>> > +          }
>>>>> > +          return "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>>> -script-javascript.xml
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/log4j-core/src/test/resource
>>>>> s/log4j-routing-routes-script-javascript.xml
>>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>> javascript.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..e672aea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>>> javascript.xml
>>>>> > @@ -0,0 +1,40 @@
>>>>> > +<?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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>>> "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>> > ------------------------------------------------------------
>>>>> ----------
>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>>> b/src/site/xdoc/manual/appenders.xml
>>>>> > index 2d3d361..a87d9b7 100644
>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>>> AbstractLogEventWrapperEntity {
>>>>> >              Appender may be an appender previously configured and
>>>>> may be referenced by its name or the
>>>>> >              Appender can be dynamically created as needed. The
>>>>> RoutingAppender should be configured after any
>>>>> >              Appenders it references to allow it to shut down
>>>>> properly.
>>>>> > -          </p>
>>>>> > +           </p>
>>>>> > +           <p>
>>>>> > +             You can also configure a RoutingAppender with scripts:
>>>>> you can run a script when the appender starts
>>>>> > +             and when a route is chosen for an log event.
>>>>> > +           </p>
>>>>> >           <table>
>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>> >             <tr>
>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>>> AbstractLogEventWrapperEntity {
>>>>> >               <th>Description</th>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>filter</td>
>>>>> > +              <td>Filter</td>
>>>>> >               <td>Filter</td>
>>>>> >               <td>A Filter to determine if the event should be
>>>>> handled by this Appender. More than one Filter
>>>>> >               may be used by using a CompositeFilter.</td>
>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>>> AbstractLogEventWrapperEntity {
>>>>> >               <td>The name of the Appender.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>rewritePolicy</td>
>>>>> > +              <td>RewritePolicy</td>
>>>>> >               <td>RewritePolicy</td>
>>>>> >               <td>The RewritePolicy that will manipulate the
>>>>> LogEvent.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>routes</td>
>>>>> > +              <td>Routes</td>
>>>>> >               <td>Routes</td>
>>>>> >               <td>Contains one or more Route declarations to
>>>>> identify the criteria for choosing Appenders.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>This Script runs when Log4j starts the
>>>>> RoutingAppender and returns a String Route key to determine
>>>>> > +                the default Route.</td>
>>>>> > +            </tr>
>>>>> > +            <tr>
>>>>> >               <td>ignoreExceptions</td>
>>>>> >               <td>boolean</td>
>>>>> >               <td>The default is <code>true</code>, causing
>>>>> exceptions encountered while appending events to be
>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>>> AbstractLogEventWrapperEntity {
>>>>> >           </table>
>>>>> >           <h4>Routes</h4>
>>>>> >             <p>
>>>>> > -              The Routes element accepts a single, required
>>>>> attribute named "pattern". The pattern is evaluated
>>>>> > +              The Routes element accepts a single attribute named
>>>>> "pattern". The pattern is evaluated
>>>>> >               against all the registered Lookups and the result is
>>>>> used to select a Route. Each Route may be
>>>>> >               configured with a key. If the key matches the result
>>>>> of evaluating the pattern then that Route
>>>>> >               will be selected. If no key is specified on a Route
>>>>> then that Route is the default. Only one Route
>>>>> >               can be configured as the default.
>>>>> >             </p>
>>>>> >             <p>
>>>>> > +              The Routes element may contain a Script child
>>>>> element. If specified, the Script is run for each
>>>>> > +              log event and returns the String Route key to use.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> > +              You must specify either the pattern attribute or the
>>>>> Script element, but not both.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> >               Each Route must reference an Appender. If the Route
>>>>> contains a ref attribute then the
>>>>> >               Route will reference an Appender that was defined in
>>>>> the configuration. If the Route contains an
>>>>> >               Appender definition then an Appender will be created
>>>>> within the context of the RoutingAppender and
>>>>> >
>>>>> >
>>>>>
>>>>>
>>>>>
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>>> <gg...@apache.org>
>>>> Java Persistence with Hibernate, Second Edition
>>>> <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com
>>>> Home: http://garygregory.com/
>>>> Tweet! http://twitter.com/GaryGregory
>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>> Java Persistence with Hibernate, Second Edition
>>> <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
>>>
>>>
>>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>>
>>
>
>


-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
And obviously the Bindings variable should not be a class member.

Ralph

> On Sep 14, 2016, at 4:33 PM, Ralph Goers <ra...@dslextreme.com> wrote:
> 
> Oops. Never mind. You have if (bindings == null) …  Don’t do that.
> 
> Ralph
> 
>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>> 
>> That's not how I have now. I'll revisit...
>> 
>> 
>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>> A new Binding is created for every script execution so thread safety is not a problem.
>> 
>> Ralph
>> 
>>> On Sep 14, 2016, at 4:09 PM, Remko Popma <remko.popma@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>> If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 
>>> 
>>> Sent from my iPhone
>>> 
>>> On 2016/09/15, at 6:24, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>>> Ralph: Thank you for the guidance on this topic.
>>>> 
>>>> I'll tackle the documentation update tonight. 
>>>> 
>>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
>>>> 
>>>> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
>>>> 
>>>> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
>>>> 
>>>> Gary
>>>> 
>>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>> Sounds good.
>>>> 
>>>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>>>> 
>>>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
>>>> 
>>>> Ralph
>>>> 
>>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>> 
>>>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>>>> 
>>>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>>>> 
>>>>> I'll add LogEvent support next...
>>>>> 
>>>>> Gary
>>>>> 
>>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>> OK - It wasn’t in there when I looked last night.
>>>>> 
>>>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>>>> 
>>>>> Ralph
>>>>> 
>>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>>> 
>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>>> Gary,
>>>>>> 
>>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>>>> 
>>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>>> 
>>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>>       * @return the pattern.
>>>>>>       */
>>>>>>      public String getPattern() {
>>>>>> +        if (patternScript != null) {
>>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>>> +            bindings.put("configuration", configuration);
>>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>>> +            return Objects.toString(object, null);
>>>>>> +        }
>>>>>>          return pattern;
>>>>>>      }
>>>>>> 
>>>>>> Gary
>>>>>>  
>>>>>> 
>>>>>> Ralph
>>>>>> 
>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>>>>> >
>>>>>> > Repository: logging-log4j2
>>>>>> > Updated Branches:
>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>> >
>>>>>> >
>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>>>> > in a Routes element.
>>>>>> >
>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>>>>> >
>>>>>> > Branch: refs/heads/master
>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>> > Parents: 3846e2a
>>>>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>> >
>>>>>> > ----------------------------------------------------------------------
>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>> > ----------------------------------------------------------------------
>>>>>> >
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > index c95b64a..33fccd7 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>>>> >
>>>>>> > import java.util.Objects;
>>>>>> >
>>>>>> > +import javax.script.SimpleBindings;
>>>>>> > +
>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>> > 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.PluginBuilderFactory;
>>>>>> > +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.validation.constraints.Required;
>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>> >
>>>>>> > /**
>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>> >
>>>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>> >
>>>>>> > +        @PluginConfiguration
>>>>>> > +        private Configuration configuration;
>>>>>> > +
>>>>>> >         @PluginAttribute("pattern")
>>>>>> >         private String pattern;
>>>>>> >
>>>>>> > -        @PluginElement("Routes")
>>>>>> > +        @PluginElement("Script")
>>>>>> > +        private AbstractScript patternScript;
>>>>>> > +
>>>>>> > +        @PluginElement("Routes")
>>>>>> > +        @Required
>>>>>> >         private Route[] routes;
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public Routes build() {
>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>> > -                LOGGER.error("No routes configured");
>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new Routes(pattern, routes);
>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>>>> > +            }
>>>>>> > +            if (patternScript != null) {
>>>>>> > +                if (configuration == null) {
>>>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>>>> > +                } else {
>>>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>>>> > +                }
>>>>>> > +            }
>>>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Configuration getConfiguration() {
>>>>>> > +            return configuration;
>>>>>> >         }
>>>>>> >
>>>>>> >         public String getPattern() {
>>>>>> >             return pattern;
>>>>>> >         }
>>>>>> >
>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>> > +            return patternScript;
>>>>>> > +        }
>>>>>> > +
>>>>>> >         public Route[] getRoutes() {
>>>>>> >             return routes;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>>>> > +            this.configuration = configuration;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>>>> >             this.pattern = pattern;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>>>> > +            this.patternScript = patternScript;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>>>> >             this.routes = routes;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > +
>>>>>> > +    /**
>>>>>> > +     * Creates the Routes.
>>>>>> > +     * @param pattern The pattern.
>>>>>> > +     * @param routes An array of Route elements.
>>>>>> > +     * @return The Routes container.
>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > +     */
>>>>>> > +    @Deprecated
>>>>>> > +    public static Routes createRoutes(
>>>>>> > +            final String pattern,
>>>>>> > +            final Route... routes) {
>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>> > +            LOGGER.error("No routes configured");
>>>>>> > +            return null;
>>>>>> > +        }
>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>> > +    }
>>>>>> > +
>>>>>> >     @PluginBuilderFactory
>>>>>> >     public static Builder newBuilder() {
>>>>>> >         return new Builder();
>>>>>> >     }
>>>>>> > -
>>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > -
>>>>>> > +
>>>>>> > +    private final Configuration configuration;
>>>>>> > +
>>>>>> >     private final String pattern;
>>>>>> >
>>>>>> > +    private final AbstractScript patternScript;
>>>>>> > +
>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>> >     private final Route[] routes;
>>>>>> >
>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>> > +        this.configuration = configuration;
>>>>>> > +        this.patternScript = patternScript;
>>>>>> >         this.pattern = pattern;
>>>>>> >         this.routes = routes;
>>>>>> >     }
>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>> >      * @return the pattern.
>>>>>> >      */
>>>>>> >     public String getPattern() {
>>>>>> > +        if (patternScript != null) {
>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>> > +            bindings.put("configuration", configuration);
>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>>> > +            return Objects.toString(object, null);
>>>>>> > +        }
>>>>>> >         return pattern;
>>>>>> >     }
>>>>>> >
>>>>>> > -    public Route getRoute(String key) {
>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>> > -            final Route route = routes[i];
>>>>>> > +    /**
>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>>>> > +     */
>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>> > +        return patternScript;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Route getRoute(final String key) {
>>>>>> > +        for (final Route route : routes) {
>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>> >                 return route;
>>>>>> >             }
>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > -    /**
>>>>>> > -     * Creates the Routes.
>>>>>> > -     * @param pattern The pattern.
>>>>>> > -     * @param routes An array of Route elements.
>>>>>> > -     * @return The Routes container.
>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > -     */
>>>>>> > -    @Deprecated
>>>>>> > -    public static Routes createRoutes(
>>>>>> > -            final String pattern,
>>>>>> > -            final Route... routes) {
>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>> > -            LOGGER.error("No routes configured");
>>>>>> > -            return null;
>>>>>> > -        }
>>>>>> > -        return new Routes(pattern, routes);
>>>>>> > -    }
>>>>>> > -
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > index 4471333..78fddbc 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public RoutingAppender build() {
>>>>>> > -            if (getName() == null) {
>>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>>> > +            final String name = getName();
>>>>>> > +            if (name == null) {
>>>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> >             if (routes == null) {
>>>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>>> >         }
>>>>>> >
>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >     public void start() {
>>>>>> >         if (defaultRouteScript != null) {
>>>>>> >             if (configuration == null) {
>>>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>>>> >             } else {
>>>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>> >         return rewritePolicy;
>>>>>> >     }
>>>>>> > +
>>>>>> > +    public Routes getRoutes() {
>>>>>> > +        return routes;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Configuration getConfiguration() {
>>>>>> > +        return configuration;
>>>>>> > +    }
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > new file mode 100644
>>>>>> > index 0000000..7d90f6b
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > @@ -0,0 +1,130 @@
>>>>>> > +/*
>>>>>> > + * 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 <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.appender.routing;
>>>>>> > +
>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>> > +
>>>>>> > +import java.util.List;
>>>>>> > +import java.util.Map;
>>>>>> > +
>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>> > +import org.junit.Assert;
>>>>>> > +import org.junit.Rule;
>>>>>> > +import org.junit.Test;
>>>>>> > +import org.junit.runner.RunWith;
>>>>>> > +import org.junit.runners.Parameterized;
>>>>>> > +
>>>>>> > +/**
>>>>>> > + *
>>>>>> > + */
>>>>>> > +@RunWith(Parameterized.class)
>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>> > +
>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>> > +    public static String[] getParameters() {
>>>>>> > +        return new String[] {
>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Rule
>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>> > +
>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private ListAppender getListAppender() {
>>>>>> > +        final String key = "Service2";
>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>>>> > +        return listAppender;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private void logAndCheck() {
>>>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>>>> > +        logger.error("Hello");
>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>> > +        assertNotNull("No events generated", list);
>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>>>> > +        logger.error("World");
>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>> > +    public void testAppenderAbsence() {
>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testListAppenderPresence() {
>>>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoPurgePolicy() {
>>>>>> > +        // No PurgePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoRewritePolicy() {
>>>>>> > +        // No RewritePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>> > +        Assert.assertNotNull(routes);
>>>>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>> > +        getRoutingAppender();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence1() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence2() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +}
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..83121ea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>> > +            return "Service2";
>>>>>> > +          }
>>>>>> > +          return "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..e672aea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>> > @@ -0,0 +1,40 @@
>>>>>> > +<?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 <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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>>>> >              Appenders it references to allow it to shut down properly.
>>>>>> > -          </p>
>>>>>> > +           </p>
>>>>>> > +           <p>
>>>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>>>> > +             and when a route is chosen for an log event.
>>>>>> > +           </p>
>>>>>> >           <table>
>>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>>> >             <tr>
>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >               <th>Description</th>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>filter</td>
>>>>>> > +              <td>Filter</td>
>>>>>> >               <td>Filter</td>
>>>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >               <td>The name of the Appender.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>rewritePolicy</td>
>>>>>> > +              <td>RewritePolicy</td>
>>>>>> >               <td>RewritePolicy</td>
>>>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>routes</td>
>>>>>> > +              <td>Routes</td>
>>>>>> >               <td>Routes</td>
>>>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>>>> > +                the default Route.</td>
>>>>>> > +            </tr>
>>>>>> > +            <tr>
>>>>>> >               <td>ignoreExceptions</td>
>>>>>> >               <td>boolean</td>
>>>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >           </table>
>>>>>> >           <h4>Routes</h4>
>>>>>> >             <p>
>>>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>>>> >               can be configured as the default.
>>>>>> >             </p>
>>>>>> >             <p>
>>>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>>>> > +              log event and returns the String Route key to use.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>>>> >
>>>>>> >
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> -- 
>>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>>> 
>>>>> 
>>>>> 
>>>>> -- 
>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>> 
>>>> 
>>>> 
>>>> -- 
>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
> 


Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
Oops. Never mind. You have if (bindings == null) …  Don’t do that.

Ralph

> On Sep 14, 2016, at 4:29 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> That's not how I have now. I'll revisit...
> 
> 
> On Sep 14, 2016 4:27 PM, "Ralph Goers" <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> A new Binding is created for every script execution so thread safety is not a problem.
> 
> Ralph
> 
>> On Sep 14, 2016, at 4:09 PM, Remko Popma <remko.popma@gmail.com <ma...@gmail.com>> wrote:
>> 
>> If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 
>> 
>> Sent from my iPhone
>> 
>> On 2016/09/15, at 6:24, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>> 
>>> Ralph: Thank you for the guidance on this topic.
>>> 
>>> I'll tackle the documentation update tonight. 
>>> 
>>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
>>> 
>>> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
>>> 
>>> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
>>> 
>>> Gary
>>> 
>>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>> Sounds good.
>>> 
>>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>>> 
>>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
>>> 
>>> Ralph
>>> 
>>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>>> 
>>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>>> 
>>>> I'll add LogEvent support next...
>>>> 
>>>> Gary
>>>> 
>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>> OK - It wasn’t in there when I looked last night.
>>>> 
>>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>>> 
>>>> Ralph
>>>> 
>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>>> 
>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>>> Gary,
>>>>> 
>>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>>> 
>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>> 
>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>       * @return the pattern.
>>>>>       */
>>>>>      public String getPattern() {
>>>>> +        if (patternScript != null) {
>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>> +            bindings.put("configuration", configuration);
>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>> +            return Objects.toString(object, null);
>>>>> +        }
>>>>>          return pattern;
>>>>>      }
>>>>> 
>>>>> Gary
>>>>>  
>>>>> 
>>>>> Ralph
>>>>> 
>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>>>> >
>>>>> > Repository: logging-log4j2
>>>>> > Updated Branches:
>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>> >
>>>>> >
>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>>> > in a Routes element.
>>>>> >
>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>>>> >
>>>>> > Branch: refs/heads/master
>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>> > Parents: 3846e2a
>>>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>> >
>>>>> > ----------------------------------------------------------------------
>>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>> > ----------------------------------------------------------------------
>>>>> >
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > index c95b64a..33fccd7 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>>> >
>>>>> > import java.util.Objects;
>>>>> >
>>>>> > +import javax.script.SimpleBindings;
>>>>> > +
>>>>> > import org.apache.logging.log4j.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>> > 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.PluginBuilderFactory;
>>>>> > +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.validation.constraints.Required;
>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>> >
>>>>> > /**
>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>> >
>>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>> >
>>>>> > +        @PluginConfiguration
>>>>> > +        private Configuration configuration;
>>>>> > +
>>>>> >         @PluginAttribute("pattern")
>>>>> >         private String pattern;
>>>>> >
>>>>> > -        @PluginElement("Routes")
>>>>> > +        @PluginElement("Script")
>>>>> > +        private AbstractScript patternScript;
>>>>> > +
>>>>> > +        @PluginElement("Routes")
>>>>> > +        @Required
>>>>> >         private Route[] routes;
>>>>> >
>>>>> >         @Override
>>>>> >         public Routes build() {
>>>>> >             if (routes == null || routes.length == 0) {
>>>>> > -                LOGGER.error("No routes configured");
>>>>> > +                LOGGER.error("No Routes configured.");
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new Routes(pattern, routes);
>>>>> > +            if (patternScript != null && pattern != null) {
>>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>>> > +            }
>>>>> > +            if (patternScript != null) {
>>>>> > +                if (configuration == null) {
>>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>>> > +                } else {
>>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>>> > +                }
>>>>> > +            }
>>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>>> > +        }
>>>>> > +
>>>>> > +        public Configuration getConfiguration() {
>>>>> > +            return configuration;
>>>>> >         }
>>>>> >
>>>>> >         public String getPattern() {
>>>>> >             return pattern;
>>>>> >         }
>>>>> >
>>>>> > +        public AbstractScript getPatternScript() {
>>>>> > +            return patternScript;
>>>>> > +        }
>>>>> > +
>>>>> >         public Route[] getRoutes() {
>>>>> >             return routes;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>>> > +            this.configuration = configuration;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>>> >             this.pattern = pattern;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>>> > +            this.patternScript = patternScript;
>>>>> > +            return this;
>>>>> > +        }
>>>>> > +
>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>>> >             this.routes = routes;
>>>>> >             return this;
>>>>> >         }
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > +
>>>>> > +    /**
>>>>> > +     * Creates the Routes.
>>>>> > +     * @param pattern The pattern.
>>>>> > +     * @param routes An array of Route elements.
>>>>> > +     * @return The Routes container.
>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > +     */
>>>>> > +    @Deprecated
>>>>> > +    public static Routes createRoutes(
>>>>> > +            final String pattern,
>>>>> > +            final Route... routes) {
>>>>> > +        if (routes == null || routes.length == 0) {
>>>>> > +            LOGGER.error("No routes configured");
>>>>> > +            return null;
>>>>> > +        }
>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>> > +    }
>>>>> > +
>>>>> >     @PluginBuilderFactory
>>>>> >     public static Builder newBuilder() {
>>>>> >         return new Builder();
>>>>> >     }
>>>>> > -
>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>> > -
>>>>> > +
>>>>> > +    private final Configuration configuration;
>>>>> > +
>>>>> >     private final String pattern;
>>>>> >
>>>>> > +    private final AbstractScript patternScript;
>>>>> > +
>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>> >     private final Route[] routes;
>>>>> >
>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>> > +        this.configuration = configuration;
>>>>> > +        this.patternScript = patternScript;
>>>>> >         this.pattern = pattern;
>>>>> >         this.routes = routes;
>>>>> >     }
>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>> >      * @return the pattern.
>>>>> >      */
>>>>> >     public String getPattern() {
>>>>> > +        if (patternScript != null) {
>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>> > +            bindings.put("configuration", configuration);
>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>> > +            return Objects.toString(object, null);
>>>>> > +        }
>>>>> >         return pattern;
>>>>> >     }
>>>>> >
>>>>> > -    public Route getRoute(String key) {
>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>> > -            final Route route = routes[i];
>>>>> > +    /**
>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>>> > +     */
>>>>> > +    public AbstractScript getPatternScript() {
>>>>> > +        return patternScript;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Route getRoute(final String key) {
>>>>> > +        for (final Route route : routes) {
>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>> >                 return route;
>>>>> >             }
>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>> >
>>>>> >     }
>>>>> >
>>>>> > -    /**
>>>>> > -     * Creates the Routes.
>>>>> > -     * @param pattern The pattern.
>>>>> > -     * @param routes An array of Route elements.
>>>>> > -     * @return The Routes container.
>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>> > -     */
>>>>> > -    @Deprecated
>>>>> > -    public static Routes createRoutes(
>>>>> > -            final String pattern,
>>>>> > -            final Route... routes) {
>>>>> > -        if (routes == null || routes.length == 0) {
>>>>> > -            LOGGER.error("No routes configured");
>>>>> > -            return null;
>>>>> > -        }
>>>>> > -        return new Routes(pattern, routes);
>>>>> > -    }
>>>>> > -
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > index 4471333..78fddbc 100644
>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >
>>>>> >         @Override
>>>>> >         public RoutingAppender build() {
>>>>> > -            if (getName() == null) {
>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>> > +            final String name = getName();
>>>>> > +            if (name == null) {
>>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>>> >                 return null;
>>>>> >             }
>>>>> >             if (routes == null) {
>>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>>> >                 return null;
>>>>> >             }
>>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>> >         }
>>>>> >
>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >     public void start() {
>>>>> >         if (defaultRouteScript != null) {
>>>>> >             if (configuration == null) {
>>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>>> >             } else {
>>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>> >         return rewritePolicy;
>>>>> >     }
>>>>> > +
>>>>> > +    public Routes getRoutes() {
>>>>> > +        return routes;
>>>>> > +    }
>>>>> > +
>>>>> > +    public Configuration getConfiguration() {
>>>>> > +        return configuration;
>>>>> > +    }
>>>>> > }
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>> > new file mode 100644
>>>>> > index 0000000..7d90f6b
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>> > @@ -0,0 +1,130 @@
>>>>> > +/*
>>>>> > + * 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 <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.appender.routing;
>>>>> > +
>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>> > +import static org.junit.Assert.assertTrue;
>>>>> > +
>>>>> > +import java.util.List;
>>>>> > +import java.util.Map;
>>>>> > +
>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>> > +import org.junit.Assert;
>>>>> > +import org.junit.Rule;
>>>>> > +import org.junit.Test;
>>>>> > +import org.junit.runner.RunWith;
>>>>> > +import org.junit.runners.Parameterized;
>>>>> > +
>>>>> > +/**
>>>>> > + *
>>>>> > + */
>>>>> > +@RunWith(Parameterized.class)
>>>>> > +public class RoutesScriptAppenderTest {
>>>>> > +
>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>> > +    public static String[] getParameters() {
>>>>> > +        return new String[] {
>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>> > +    }
>>>>> > +
>>>>> > +    @Rule
>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>> > +
>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>>> > +    }
>>>>> > +
>>>>> > +    private ListAppender getListAppender() {
>>>>> > +        final String key = "Service2";
>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>>> > +        return listAppender;
>>>>> > +    }
>>>>> > +
>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>>> > +    }
>>>>> > +
>>>>> > +    private void logAndCheck() {
>>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>>> > +        logger.error("Hello");
>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>> > +        assertNotNull("No events generated", list);
>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>>> > +        logger.error("World");
>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test(expected = AssertionError.class)
>>>>> > +    public void testAppenderAbsence() {
>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testListAppenderPresence() {
>>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoPurgePolicy() {
>>>>> > +        // No PurgePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testNoRewritePolicy() {
>>>>> > +        // No RewritePolicy in this test
>>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>> > +        Assert.assertNotNull(routes);
>>>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingAppenderPresence() {
>>>>> > +        getRoutingAppender();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence1() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +
>>>>> > +    @Test
>>>>> > +    public void testRoutingPresence2() {
>>>>> > +        logAndCheck();
>>>>> > +    }
>>>>> > +}
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..83121ea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>> > +            return "Service2";
>>>>> > +          }
>>>>> > +          return "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>> > new file mode 100644
>>>>> > index 0000000..e672aea
>>>>> > --- /dev/null
>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>> > @@ -0,0 +1,40 @@
>>>>> > +<?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 <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="WARN" name="RoutingTest">
>>>>> > +  <Appenders>
>>>>> > +    <Routing name="Routing">
>>>>> > +      <Routes>
>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>>> > +        </Script>
>>>>> > +        <Route key="Service1">
>>>>> > +          <List name="List1" />
>>>>> > +        </Route>
>>>>> > +        <Route key="Service2">
>>>>> > +          <List name="List2" />
>>>>> > +        </Route>
>>>>> > +      </Routes>
>>>>> > +    </Routing>
>>>>> > +  </Appenders>
>>>>> > +  <Loggers>
>>>>> > +    <Root level="error">
>>>>> > +      <AppenderRef ref="Routing" />
>>>>> > +    </Root>
>>>>> > +  </Loggers>
>>>>> > +</Configuration>
>>>>> > \ No newline at end of file
>>>>> >
>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>>>> > ----------------------------------------------------------------------
>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>>> > index 2d3d361..a87d9b7 100644
>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>>> >              Appenders it references to allow it to shut down properly.
>>>>> > -          </p>
>>>>> > +           </p>
>>>>> > +           <p>
>>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>>> > +             and when a route is chosen for an log event.
>>>>> > +           </p>
>>>>> >           <table>
>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>> >             <tr>
>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >               <th>Description</th>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>filter</td>
>>>>> > +              <td>Filter</td>
>>>>> >               <td>Filter</td>
>>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>>> >               may be used by using a CompositeFilter.</td>
>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >               <td>The name of the Appender.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>rewritePolicy</td>
>>>>> > +              <td>RewritePolicy</td>
>>>>> >               <td>RewritePolicy</td>
>>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > -              <td>routes</td>
>>>>> > +              <td>Routes</td>
>>>>> >               <td>Routes</td>
>>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>>> >             </tr>
>>>>> >             <tr>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>Script</td>
>>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>>> > +                the default Route.</td>
>>>>> > +            </tr>
>>>>> > +            <tr>
>>>>> >               <td>ignoreExceptions</td>
>>>>> >               <td>boolean</td>
>>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>> >           </table>
>>>>> >           <h4>Routes</h4>
>>>>> >             <p>
>>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>>> >               can be configured as the default.
>>>>> >             </p>
>>>>> >             <p>
>>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>>> > +              log event and returns the String Route key to use.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>>> > +            </p>
>>>>> > +            <p>
>>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>>> >
>>>>> >
>>>>> 
>>>>> 
>>>>> 
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>>>> 
>>>>> 
>>>>> 
>>>>> 
>>>>> -- 
>>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>>> 
>>>> 
>>>> 
>>>> -- 
>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>> 
>>> 
>>> 
>>> -- 
>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>


Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
That's not how I have now. I'll revisit...

On Sep 14, 2016 4:27 PM, "Ralph Goers" <ra...@dslextreme.com> wrote:

> A new Binding is created for every script execution so thread safety is
> not a problem.
>
> Ralph
>
> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com> wrote:
>
> If the Binding is shared between threads (and therefore not thread-safe),
> you could put the LogEvent in a ThreadLocal.
>
> Sent from my iPhone
>
> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
>
> Ralph: Thank you for the guidance on this topic.
>
> I'll tackle the documentation update tonight.
>
> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that,
> I can take a look.
>
> I am wondering in general about the performance difference from an app POV
> between a plain appender and one appender nested in a RoutingAppender.
>
> I am also wondering about any concurrency issue passing a LogEvent to a
> Script. Can a LogEvent be skipped when multiple threads use the same
> RoutingAppender?
>
> Gary
>
> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ra...@dslextreme.com>
> wrote:
>
>> Sounds good.
>>
>> When documenting that you should make it clear that the Map is shared so
>> that every thread is seeing the same Map. Users need to be aware that they
>> cannot put things in the map and expect it to only be available for that
>> single event.
>>
>> The last, and probably trickiest part, is going to be making it so
>> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
>> really figured out how to do that.
>>
>> Ralph
>>
>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com>
>> wrote:
>>
>> The RoutingAppender Scripts now share a Bindings instance which contains
>> a ConcurrentMap keyed under "staticVariables". The Bindings instance is
>> tracked as a RoutingAppender and Routes ivar.
>>
>> I created an abstract superclass for (private) ScriptRunner
>> implementations which holds on to the ConcurrentMap. The map can act as a
>> set of static/global variables for that script and can be shared through a
>> Bindings instance. The private ScriptRunner has new method
>> ScriptManager.ScriptRunner.createBindings(). Right now there is no
>> script specific data added to the Bindings, but there could be in the
>> future.
>>
>> I'll add LogEvent support next...
>>
>> Gary
>>
>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ra...@dslextreme.com>
>> wrote:
>>
>>> OK - It wasn’t in there when I looked last night.
>>>
>>> A couple other things. A ConcurrentMap should be created and passed to
>>> the init script and the routing script so that the init script can pass
>>> variables to the routing script. Also, the routing script really needs to
>>> be passed the logEvent so it can route based on data within it.
>>>
>>> Ralph
>>>
>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>>> wrote:
>>>
>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.
>>> com> wrote:
>>>
>>>> Gary,
>>>>
>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for
>>>> the Routes class to execute the script in the call to getPattern and return
>>>> the result if there is a script?
>>>>
>>>
>>> That's what Routes.getPattern() does (see this very commit thread):
>>>
>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>       * @return the pattern.
>>>       */
>>>      public String getPattern() {
>>> +        if (patternScript != null) {
>>> +            final SimpleBindings bindings = new SimpleBindings();
>>> +            bindings.put("configuration", configuration);
>>> +            bindings.put("statusLogger", LOGGER);
>>> +            final Object object = configuration.getScriptManager
>>> ().execute(patternScript.getName(), bindings);
>>> +            return Objects.toString(object, null);
>>> +        }
>>>          return pattern;
>>>      }
>>>
>>> Gary
>>>
>>>
>>>>
>>>> Ralph
>>>>
>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>> >
>>>> > Repository: logging-log4j2
>>>> > Updated Branches:
>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>> >
>>>> >
>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>>> Script
>>>> > in a Routes element.
>>>> >
>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>>> 2/commit/e0f29d9a
>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>> tree/e0f29d9a
>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>>> diff/e0f29d9a
>>>> >
>>>> > Branch: refs/heads/master
>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>> > Parents: 3846e2a
>>>> > Author: Gary Gregory <gg...@apache.org>
>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>> >
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>>> ++++++++++++-----
>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>>> +++++++++++++++++++
>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> >
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>> core/appender/routing/Routes.java
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/log4j-core/src/main/java/org
>>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/Routes.java
>>>> > index c95b64a..33fccd7 100644
>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/Routes.java
>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/Routes.java
>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>>> appender.routing;
>>>> >
>>>> > import java.util.Objects;
>>>> >
>>>> > +import javax.script.SimpleBindings;
>>>> > +
>>>> > import org.apache.logging.log4j.Logger;
>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>> > 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.PluginBuilderFa
>>>> ctory;
>>>> > +import org.apache.logging.log4j.core.config.plugins.PluginConfigura
>>>> tion;
>>>> > import org.apache.logging.log4j.core.config.plugins.PluginElement;
>>>> > +import org.apache.logging.log4j.core.config.plugins.validation.cons
>>>> traints.Required;
>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>> >
>>>> > /**
>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>> >
>>>> >     public static class Builder implements
>>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>> >
>>>> > +        @PluginConfiguration
>>>> > +        private Configuration configuration;
>>>> > +
>>>> >         @PluginAttribute("pattern")
>>>> >         private String pattern;
>>>> >
>>>> > -        @PluginElement("Routes")
>>>> > +        @PluginElement("Script")
>>>> > +        private AbstractScript patternScript;
>>>> > +
>>>> > +        @PluginElement("Routes")
>>>> > +        @Required
>>>> >         private Route[] routes;
>>>> >
>>>> >         @Override
>>>> >         public Routes build() {
>>>> >             if (routes == null || routes.length == 0) {
>>>> > -                LOGGER.error("No routes configured");
>>>> > +                LOGGER.error("No Routes configured.");
>>>> >                 return null;
>>>> >             }
>>>> > -            return new Routes(pattern, routes);
>>>> > +            if (patternScript != null && pattern != null) {
>>>> > +                LOGGER.warn("In a Routes element, you must configure
>>>> either a Script element or a pattern attribute.");
>>>> > +            }
>>>> > +            if (patternScript != null) {
>>>> > +                if (configuration == null) {
>>>> > +                    LOGGER.error("No Configuration defined for
>>>> Routes; required for Script");
>>>> > +                } else {
>>>> > +                    configuration.getScriptManager
>>>> ().addScript(patternScript);
>>>> > +                }
>>>> > +            }
>>>> > +            return new Routes(configuration, patternScript, pattern,
>>>> routes);
>>>> > +        }
>>>> > +
>>>> > +        public Configuration getConfiguration() {
>>>> > +            return configuration;
>>>> >         }
>>>> >
>>>> >         public String getPattern() {
>>>> >             return pattern;
>>>> >         }
>>>> >
>>>> > +        public AbstractScript getPatternScript() {
>>>> > +            return patternScript;
>>>> > +        }
>>>> > +
>>>> >         public Route[] getRoutes() {
>>>> >             return routes;
>>>> >         }
>>>> >
>>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>>> String pattern) {
>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>>> final Configuration configuration) {
>>>> > +            this.configuration = configuration;
>>>> > +            return this;
>>>> > +        }
>>>> > +
>>>> > +        public Builder withPattern(@SuppressWarnings("hiding")
>>>> final String pattern) {
>>>> >             this.pattern = pattern;
>>>> >             return this;
>>>> >         }
>>>> >
>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>>> Route[] routes) {
>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>>> final AbstractScript patternScript) {
>>>> > +            this.patternScript = patternScript;
>>>> > +            return this;
>>>> > +        }
>>>> > +
>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final
>>>> Route[] routes) {
>>>> >             this.routes = routes;
>>>> >             return this;
>>>> >         }
>>>> >
>>>> >     }
>>>> >
>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>> > +
>>>> > +    /**
>>>> > +     * Creates the Routes.
>>>> > +     * @param pattern The pattern.
>>>> > +     * @param routes An array of Route elements.
>>>> > +     * @return The Routes container.
>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>> > +     */
>>>> > +    @Deprecated
>>>> > +    public static Routes createRoutes(
>>>> > +            final String pattern,
>>>> > +            final Route... routes) {
>>>> > +        if (routes == null || routes.length == 0) {
>>>> > +            LOGGER.error("No routes configured");
>>>> > +            return null;
>>>> > +        }
>>>> > +        return new Routes(null, null, pattern, routes);
>>>> > +    }
>>>> > +
>>>> >     @PluginBuilderFactory
>>>> >     public static Builder newBuilder() {
>>>> >         return new Builder();
>>>> >     }
>>>> > -
>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>> > -
>>>> > +
>>>> > +    private final Configuration configuration;
>>>> > +
>>>> >     private final String pattern;
>>>> >
>>>> > +    private final AbstractScript patternScript;
>>>> > +
>>>> >     // TODO Why not make this a Map or add a Map.
>>>> >     private final Route[] routes;
>>>> >
>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>> > +    private Routes(final Configuration configuration, final
>>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>>> > +        this.configuration = configuration;
>>>> > +        this.patternScript = patternScript;
>>>> >         this.pattern = pattern;
>>>> >         this.routes = routes;
>>>> >     }
>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>> >      * @return the pattern.
>>>> >      */
>>>> >     public String getPattern() {
>>>> > +        if (patternScript != null) {
>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>> > +            bindings.put("configuration", configuration);
>>>> > +            bindings.put("statusLogger", LOGGER);
>>>> > +            final Object object = configuration.getScriptManager
>>>> ().execute(patternScript.getName(), bindings);
>>>> > +            return Objects.toString(object, null);
>>>> > +        }
>>>> >         return pattern;
>>>> >     }
>>>> >
>>>> > -    public Route getRoute(String key) {
>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>> > -            final Route route = routes[i];
>>>> > +    /**
>>>> > +     * Gets the optional script that decides which route to pick.
>>>> > +     * @return the optional script that decides which route to pick.
>>>> May be null.
>>>> > +     */
>>>> > +    public AbstractScript getPatternScript() {
>>>> > +        return patternScript;
>>>> > +    }
>>>> > +
>>>> > +    public Route getRoute(final String key) {
>>>> > +        for (final Route route : routes) {
>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>> >                 return route;
>>>> >             }
>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>> >
>>>> >     }
>>>> >
>>>> > -    /**
>>>> > -     * Creates the Routes.
>>>> > -     * @param pattern The pattern.
>>>> > -     * @param routes An array of Route elements.
>>>> > -     * @return The Routes container.
>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>> > -     */
>>>> > -    @Deprecated
>>>> > -    public static Routes createRoutes(
>>>> > -            final String pattern,
>>>> > -            final Route... routes) {
>>>> > -        if (routes == null || routes.length == 0) {
>>>> > -            LOGGER.error("No routes configured");
>>>> > -            return null;
>>>> > -        }
>>>> > -        return new Routes(pattern, routes);
>>>> > -    }
>>>> > -
>>>> > }
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>>> core/appender/routing/RoutingAppender.java
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/log4j-core/src/main/java/org
>>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/RoutingAppender.java
>>>> > index 4471333..78fddbc 100644
>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/RoutingAppender.java
>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>>> ender/routing/RoutingAppender.java
>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>>> AbstractAppender {
>>>> >
>>>> >         @Override
>>>> >         public RoutingAppender build() {
>>>> > -            if (getName() == null) {
>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>> > +            final String name = getName();
>>>> > +            if (name == null) {
>>>> > +                LOGGER.error("No name defined for this
>>>> RoutingAppender");
>>>> >                 return null;
>>>> >             }
>>>> >             if (routes == null) {
>>>> > -                LOGGER.error("No routes defined for
>>>> RoutingAppender");
>>>> > +                LOGGER.error("No routes defined for RoutingAppender
>>>> {}", name);
>>>> >                 return null;
>>>> >             }
>>>> > -            return new RoutingAppender(getName(), getFilter(),
>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>> > +            return new RoutingAppender(name, getFilter(),
>>>> isIgnoreExceptions(), routes, rewritePolicy,
>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>> >         }
>>>> >
>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>>> AbstractAppender {
>>>> >     public void start() {
>>>> >         if (defaultRouteScript != null) {
>>>> >             if (configuration == null) {
>>>> > -                error("No Configuration defined for RoutingAppender;
>>>> required for DefaultRouteScript");
>>>> > +                error("No Configuration defined for RoutingAppender;
>>>> required for Script element.");
>>>> >             } else {
>>>> >                 configuration.getScriptManage
>>>> r().addScript(defaultRouteScript);
>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>>> AbstractAppender {
>>>> >     public RewritePolicy getRewritePolicy() {
>>>> >         return rewritePolicy;
>>>> >     }
>>>> > +
>>>> > +    public Routes getRoutes() {
>>>> > +        return routes;
>>>> > +    }
>>>> > +
>>>> > +    public Configuration getConfiguration() {
>>>> > +        return configuration;
>>>> > +    }
>>>> > }
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>>> core/appender/routing/RoutesScriptAppenderTest.java
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/log4j-core/src/test/java/org
>>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>> ender/routing/RoutesScriptAppenderTest.java
>>>> > new file mode 100644
>>>> > index 0000000..7d90f6b
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>>> ender/routing/RoutesScriptAppenderTest.java
>>>> > @@ -0,0 +1,130 @@
>>>> > +/*
>>>> > + * 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.appender.routing;
>>>> > +
>>>> > +import static org.junit.Assert.assertNotNull;
>>>> > +import static org.junit.Assert.assertTrue;
>>>> > +
>>>> > +import java.util.List;
>>>> > +import java.util.Map;
>>>> > +
>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>> > +import org.apache.logging.log4j.core.Logger;
>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>> > +import org.junit.Assert;
>>>> > +import org.junit.Rule;
>>>> > +import org.junit.Test;
>>>> > +import org.junit.runner.RunWith;
>>>> > +import org.junit.runners.Parameterized;
>>>> > +
>>>> > +/**
>>>> > + *
>>>> > + */
>>>> > +@RunWith(Parameterized.class)
>>>> > +public class RoutesScriptAppenderTest {
>>>> > +
>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>> > +    public static String[] getParameters() {
>>>> > +        return new String[] {
>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>> > +    }
>>>> > +
>>>> > +    @Rule
>>>> > +    public final LoggerContextRule loggerContextRule;
>>>> > +
>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>>>> on);
>>>> > +    }
>>>> > +
>>>> > +    private ListAppender getListAppender() {
>>>> > +        final String key = "Service2";
>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>> > +        final Map<String, AppenderControl> appenders =
>>>> routingAppender.getAppenders();
>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>> > +        assertNotNull("No appender control generated for '" + key +
>>>> "'; appenders = " + appenders, appenderControl);
>>>> > +        final ListAppender listAppender = (ListAppender)
>>>> appenderControl.getAppender();
>>>> > +        return listAppender;
>>>> > +    }
>>>> > +
>>>> > +    private RoutingAppender getRoutingAppender() {
>>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>>> RoutingAppender.class);
>>>> > +    }
>>>> > +
>>>> > +    private void logAndCheck() {
>>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>>> utesScriptAppenderTest.class);
>>>> > +        logger.error("Hello");
>>>> > +        final ListAppender listAppender = getListAppender();
>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>> > +        assertNotNull("No events generated", list);
>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " +
>>>> list.size(), list.size() == 1);
>>>> > +        logger.error("World");
>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " +
>>>> list.size(), list.size() == 2);
>>>> > +    }
>>>> > +
>>>> > +    @Test(expected = AssertionError.class)
>>>> > +    public void testAppenderAbsence() {
>>>> > +        loggerContextRule.getListAppender("List1");
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testListAppenderPresence() {
>>>> > +        // No appender until an event is routed, even thought we
>>>> initialized the default route on startup.
>>>> > +        Assert.assertNull("No appender control generated",
>>>> getRoutingAppender().getAppenders().get("Service2"));
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testNoPurgePolicy() {
>>>> > +        // No PurgePolicy in this test
>>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>>> getRoutingAppender().getPurgePolicy());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testNoRewritePolicy() {
>>>> > +        // No RewritePolicy in this test
>>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>>> getRoutingAppender().getRewritePolicy());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingAppenderRoutes() {
>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>> > +        Assert.assertNotNull(routes);
>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingAppenderPresence() {
>>>> > +        getRoutingAppender();
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingPresence1() {
>>>> > +        logAndCheck();
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingPresence2() {
>>>> > +        logAndCheck();
>>>> > +    }
>>>> > +}
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>> -script-groovy.xml
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/log4j-core/src/test/resource
>>>> s/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resource
>>>> s/log4j-routing-routes-script-groovy.xml
>>>> > new file mode 100644
>>>> > index 0000000..83121ea
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>> groovy.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="WARN" name="RoutingTest">
>>>> > +  <Appenders>
>>>> > +    <Routing name="Routing">
>>>> > +      <Routes>
>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>> > +            return "Service2";
>>>> > +          }
>>>> > +          return "Service1";]]>
>>>> > +        </Script>
>>>> > +        <Route key="Service1">
>>>> > +          <List name="List1" />
>>>> > +        </Route>
>>>> > +        <Route key="Service2">
>>>> > +          <List name="List2" />
>>>> > +        </Route>
>>>> > +      </Routes>
>>>> > +    </Routing>
>>>> > +  </Appenders>
>>>> > +  <Loggers>
>>>> > +    <Root level="error">
>>>> > +      <AppenderRef ref="Routing" />
>>>> > +    </Root>
>>>> > +  </Loggers>
>>>> > +</Configuration>
>>>> > \ No newline at end of file
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>>> -script-javascript.xml
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/log4j-core/src/test/resource
>>>> s/log4j-routing-routes-script-javascript.xml
>>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>> javascript.xml
>>>> > new file mode 100644
>>>> > index 0000000..e672aea
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>>> javascript.xml
>>>> > @@ -0,0 +1,40 @@
>>>> > +<?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="WARN" name="RoutingTest">
>>>> > +  <Appenders>
>>>> > +    <Routing name="Routing">
>>>> > +      <Routes>
>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" :
>>>> "Service1";]]>
>>>> > +        </Script>
>>>> > +        <Route key="Service1">
>>>> > +          <List name="List1" />
>>>> > +        </Route>
>>>> > +        <Route key="Service2">
>>>> > +          <List name="List2" />
>>>> > +        </Route>
>>>> > +      </Routes>
>>>> > +    </Routing>
>>>> > +  </Appenders>
>>>> > +  <Loggers>
>>>> > +    <Root level="error">
>>>> > +      <AppenderRef ref="Routing" />
>>>> > +    </Root>
>>>> > +  </Loggers>
>>>> > +</Configuration>
>>>> > \ No newline at end of file
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>> > ------------------------------------------------------------
>>>> ----------
>>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>>> b/src/site/xdoc/manual/appenders.xml
>>>> > index 2d3d361..a87d9b7 100644
>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>>> AbstractLogEventWrapperEntity {
>>>> >              Appender may be an appender previously configured and
>>>> may be referenced by its name or the
>>>> >              Appender can be dynamically created as needed. The
>>>> RoutingAppender should be configured after any
>>>> >              Appenders it references to allow it to shut down
>>>> properly.
>>>> > -          </p>
>>>> > +           </p>
>>>> > +           <p>
>>>> > +             You can also configure a RoutingAppender with scripts:
>>>> you can run a script when the appender starts
>>>> > +             and when a route is chosen for an log event.
>>>> > +           </p>
>>>> >           <table>
>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>> >             <tr>
>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>>> AbstractLogEventWrapperEntity {
>>>> >               <th>Description</th>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>filter</td>
>>>> > +              <td>Filter</td>
>>>> >               <td>Filter</td>
>>>> >               <td>A Filter to determine if the event should be
>>>> handled by this Appender. More than one Filter
>>>> >               may be used by using a CompositeFilter.</td>
>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>>> AbstractLogEventWrapperEntity {
>>>> >               <td>The name of the Appender.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>rewritePolicy</td>
>>>> > +              <td>RewritePolicy</td>
>>>> >               <td>RewritePolicy</td>
>>>> >               <td>The RewritePolicy that will manipulate the
>>>> LogEvent.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>routes</td>
>>>> > +              <td>Routes</td>
>>>> >               <td>Routes</td>
>>>> >               <td>Contains one or more Route declarations to identify
>>>> the criteria for choosing Appenders.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > +              <td>Script</td>
>>>> > +              <td>Script</td>
>>>> > +              <td>This Script runs when Log4j starts the
>>>> RoutingAppender and returns a String Route key to determine
>>>> > +                the default Route.</td>
>>>> > +            </tr>
>>>> > +            <tr>
>>>> >               <td>ignoreExceptions</td>
>>>> >               <td>boolean</td>
>>>> >               <td>The default is <code>true</code>, causing
>>>> exceptions encountered while appending events to be
>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>>> AbstractLogEventWrapperEntity {
>>>> >           </table>
>>>> >           <h4>Routes</h4>
>>>> >             <p>
>>>> > -              The Routes element accepts a single, required
>>>> attribute named "pattern". The pattern is evaluated
>>>> > +              The Routes element accepts a single attribute named
>>>> "pattern". The pattern is evaluated
>>>> >               against all the registered Lookups and the result is
>>>> used to select a Route. Each Route may be
>>>> >               configured with a key. If the key matches the result of
>>>> evaluating the pattern then that Route
>>>> >               will be selected. If no key is specified on a Route
>>>> then that Route is the default. Only one Route
>>>> >               can be configured as the default.
>>>> >             </p>
>>>> >             <p>
>>>> > +              The Routes element may contain a Script child element.
>>>> If specified, the Script is run for each
>>>> > +              log event and returns the String Route key to use.
>>>> > +            </p>
>>>> > +            <p>
>>>> > +              You must specify either the pattern attribute or the
>>>> Script element, but not both.
>>>> > +            </p>
>>>> > +            <p>
>>>> >               Each Route must reference an Appender. If the Route
>>>> contains a ref attribute then the
>>>> >               Route will reference an Appender that was defined in
>>>> the configuration. If the Route contains an
>>>> >               Appender definition then an Appender will be created
>>>> within the context of the RoutingAppender and
>>>> >
>>>> >
>>>>
>>>>
>>>>
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>
>>>>
>>>
>>>
>>> --
>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>>> <gg...@apache.org>
>>> Java Persistence with Hibernate, Second Edition
>>> <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
>>>
>>>
>>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>>
>>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>
>
>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
A new Binding is created for every script execution so thread safety is not a problem.

Ralph

> On Sep 14, 2016, at 4:09 PM, Remko Popma <re...@gmail.com> wrote:
> 
> If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 
> 
> Sent from my iPhone
> 
> On 2016/09/15, at 6:24, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
> 
>> Ralph: Thank you for the guidance on this topic.
>> 
>> I'll tackle the documentation update tonight. 
>> 
>> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
>> 
>> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
>> 
>> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
>> 
>> Gary
>> 
>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>> Sounds good.
>> 
>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>> 
>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.
>> 
>> Ralph
>> 
>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>> 
>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>> 
>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>> 
>>> I'll add LogEvent support next...
>>> 
>>> Gary
>>> 
>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>> OK - It wasn’t in there when I looked last night.
>>> 
>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>> 
>>> Ralph
>>> 
>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>>>> 
>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>>>> Gary,
>>>> 
>>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>> 
>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>> 
>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>       * @return the pattern.
>>>>       */
>>>>      public String getPattern() {
>>>> +        if (patternScript != null) {
>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>> +            bindings.put("configuration", configuration);
>>>> +            bindings.put("statusLogger", LOGGER);
>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>> +            return Objects.toString(object, null);
>>>> +        }
>>>>          return pattern;
>>>>      }
>>>> 
>>>> Gary
>>>>  
>>>> 
>>>> Ralph
>>>> 
>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>>>> >
>>>> > Repository: logging-log4j2
>>>> > Updated Branches:
>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>> >
>>>> >
>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>> > in a Routes element.
>>>> >
>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>>>> >
>>>> > Branch: refs/heads/master
>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>> > Parents: 3846e2a
>>>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>> >
>>>> > ----------------------------------------------------------------------
>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>> > ----------------------------------------------------------------------
>>>> >
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>> > index c95b64a..33fccd7 100644
>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>> >
>>>> > import java.util.Objects;
>>>> >
>>>> > +import javax.script.SimpleBindings;
>>>> > +
>>>> > import org.apache.logging.log4j.Logger;
>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>> > 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.PluginBuilderFactory;
>>>> > +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.validation.constraints.Required;
>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>> >
>>>> > /**
>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>> >
>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>> >
>>>> > +        @PluginConfiguration
>>>> > +        private Configuration configuration;
>>>> > +
>>>> >         @PluginAttribute("pattern")
>>>> >         private String pattern;
>>>> >
>>>> > -        @PluginElement("Routes")
>>>> > +        @PluginElement("Script")
>>>> > +        private AbstractScript patternScript;
>>>> > +
>>>> > +        @PluginElement("Routes")
>>>> > +        @Required
>>>> >         private Route[] routes;
>>>> >
>>>> >         @Override
>>>> >         public Routes build() {
>>>> >             if (routes == null || routes.length == 0) {
>>>> > -                LOGGER.error("No routes configured");
>>>> > +                LOGGER.error("No Routes configured.");
>>>> >                 return null;
>>>> >             }
>>>> > -            return new Routes(pattern, routes);
>>>> > +            if (patternScript != null && pattern != null) {
>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>> > +            }
>>>> > +            if (patternScript != null) {
>>>> > +                if (configuration == null) {
>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>> > +                } else {
>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>> > +                }
>>>> > +            }
>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>> > +        }
>>>> > +
>>>> > +        public Configuration getConfiguration() {
>>>> > +            return configuration;
>>>> >         }
>>>> >
>>>> >         public String getPattern() {
>>>> >             return pattern;
>>>> >         }
>>>> >
>>>> > +        public AbstractScript getPatternScript() {
>>>> > +            return patternScript;
>>>> > +        }
>>>> > +
>>>> >         public Route[] getRoutes() {
>>>> >             return routes;
>>>> >         }
>>>> >
>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>> > +            this.configuration = configuration;
>>>> > +            return this;
>>>> > +        }
>>>> > +
>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>> >             this.pattern = pattern;
>>>> >             return this;
>>>> >         }
>>>> >
>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>> > +            this.patternScript = patternScript;
>>>> > +            return this;
>>>> > +        }
>>>> > +
>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>> >             this.routes = routes;
>>>> >             return this;
>>>> >         }
>>>> >
>>>> >     }
>>>> >
>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>> > +
>>>> > +    /**
>>>> > +     * Creates the Routes.
>>>> > +     * @param pattern The pattern.
>>>> > +     * @param routes An array of Route elements.
>>>> > +     * @return The Routes container.
>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>> > +     */
>>>> > +    @Deprecated
>>>> > +    public static Routes createRoutes(
>>>> > +            final String pattern,
>>>> > +            final Route... routes) {
>>>> > +        if (routes == null || routes.length == 0) {
>>>> > +            LOGGER.error("No routes configured");
>>>> > +            return null;
>>>> > +        }
>>>> > +        return new Routes(null, null, pattern, routes);
>>>> > +    }
>>>> > +
>>>> >     @PluginBuilderFactory
>>>> >     public static Builder newBuilder() {
>>>> >         return new Builder();
>>>> >     }
>>>> > -
>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>> > -
>>>> > +
>>>> > +    private final Configuration configuration;
>>>> > +
>>>> >     private final String pattern;
>>>> >
>>>> > +    private final AbstractScript patternScript;
>>>> > +
>>>> >     // TODO Why not make this a Map or add a Map.
>>>> >     private final Route[] routes;
>>>> >
>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>> > +        this.configuration = configuration;
>>>> > +        this.patternScript = patternScript;
>>>> >         this.pattern = pattern;
>>>> >         this.routes = routes;
>>>> >     }
>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>> >      * @return the pattern.
>>>> >      */
>>>> >     public String getPattern() {
>>>> > +        if (patternScript != null) {
>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>> > +            bindings.put("configuration", configuration);
>>>> > +            bindings.put("statusLogger", LOGGER);
>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>> > +            return Objects.toString(object, null);
>>>> > +        }
>>>> >         return pattern;
>>>> >     }
>>>> >
>>>> > -    public Route getRoute(String key) {
>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>> > -            final Route route = routes[i];
>>>> > +    /**
>>>> > +     * Gets the optional script that decides which route to pick.
>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>> > +     */
>>>> > +    public AbstractScript getPatternScript() {
>>>> > +        return patternScript;
>>>> > +    }
>>>> > +
>>>> > +    public Route getRoute(final String key) {
>>>> > +        for (final Route route : routes) {
>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>> >                 return route;
>>>> >             }
>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>> >
>>>> >     }
>>>> >
>>>> > -    /**
>>>> > -     * Creates the Routes.
>>>> > -     * @param pattern The pattern.
>>>> > -     * @param routes An array of Route elements.
>>>> > -     * @return The Routes container.
>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>> > -     */
>>>> > -    @Deprecated
>>>> > -    public static Routes createRoutes(
>>>> > -            final String pattern,
>>>> > -            final Route... routes) {
>>>> > -        if (routes == null || routes.length == 0) {
>>>> > -            LOGGER.error("No routes configured");
>>>> > -            return null;
>>>> > -        }
>>>> > -        return new Routes(pattern, routes);
>>>> > -    }
>>>> > -
>>>> > }
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>> > index 4471333..78fddbc 100644
>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>> >
>>>> >         @Override
>>>> >         public RoutingAppender build() {
>>>> > -            if (getName() == null) {
>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>> > +            final String name = getName();
>>>> > +            if (name == null) {
>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>> >                 return null;
>>>> >             }
>>>> >             if (routes == null) {
>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>> >                 return null;
>>>> >             }
>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>> >         }
>>>> >
>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>> >     public void start() {
>>>> >         if (defaultRouteScript != null) {
>>>> >             if (configuration == null) {
>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>> >             } else {
>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>> >     public RewritePolicy getRewritePolicy() {
>>>> >         return rewritePolicy;
>>>> >     }
>>>> > +
>>>> > +    public Routes getRoutes() {
>>>> > +        return routes;
>>>> > +    }
>>>> > +
>>>> > +    public Configuration getConfiguration() {
>>>> > +        return configuration;
>>>> > +    }
>>>> > }
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>> > new file mode 100644
>>>> > index 0000000..7d90f6b
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>> > @@ -0,0 +1,130 @@
>>>> > +/*
>>>> > + * 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 <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.appender.routing;
>>>> > +
>>>> > +import static org.junit.Assert.assertNotNull;
>>>> > +import static org.junit.Assert.assertTrue;
>>>> > +
>>>> > +import java.util.List;
>>>> > +import java.util.Map;
>>>> > +
>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>> > +import org.apache.logging.log4j.core.Logger;
>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>> > +import org.junit.Assert;
>>>> > +import org.junit.Rule;
>>>> > +import org.junit.Test;
>>>> > +import org.junit.runner.RunWith;
>>>> > +import org.junit.runners.Parameterized;
>>>> > +
>>>> > +/**
>>>> > + *
>>>> > + */
>>>> > +@RunWith(Parameterized.class)
>>>> > +public class RoutesScriptAppenderTest {
>>>> > +
>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>> > +    public static String[] getParameters() {
>>>> > +        return new String[] {
>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>> > +    }
>>>> > +
>>>> > +    @Rule
>>>> > +    public final LoggerContextRule loggerContextRule;
>>>> > +
>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>> > +    }
>>>> > +
>>>> > +    private ListAppender getListAppender() {
>>>> > +        final String key = "Service2";
>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>> > +        return listAppender;
>>>> > +    }
>>>> > +
>>>> > +    private RoutingAppender getRoutingAppender() {
>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>> > +    }
>>>> > +
>>>> > +    private void logAndCheck() {
>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>> > +        logger.error("Hello");
>>>> > +        final ListAppender listAppender = getListAppender();
>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>> > +        assertNotNull("No events generated", list);
>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>> > +        logger.error("World");
>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>> > +    }
>>>> > +
>>>> > +    @Test(expected = AssertionError.class)
>>>> > +    public void testAppenderAbsence() {
>>>> > +        loggerContextRule.getListAppender("List1");
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testListAppenderPresence() {
>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testNoPurgePolicy() {
>>>> > +        // No PurgePolicy in this test
>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testNoRewritePolicy() {
>>>> > +        // No RewritePolicy in this test
>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingAppenderRoutes() {
>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>> > +        Assert.assertNotNull(routes);
>>>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingAppenderPresence() {
>>>> > +        getRoutingAppender();
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingPresence1() {
>>>> > +        logAndCheck();
>>>> > +    }
>>>> > +
>>>> > +    @Test
>>>> > +    public void testRoutingPresence2() {
>>>> > +        logAndCheck();
>>>> > +    }
>>>> > +}
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>> > new file mode 100644
>>>> > index 0000000..83121ea
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>>>> > +  <Appenders>
>>>> > +    <Routing name="Routing">
>>>> > +      <Routes>
>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>> > +            return "Service2";
>>>> > +          }
>>>> > +          return "Service1";]]>
>>>> > +        </Script>
>>>> > +        <Route key="Service1">
>>>> > +          <List name="List1" />
>>>> > +        </Route>
>>>> > +        <Route key="Service2">
>>>> > +          <List name="List2" />
>>>> > +        </Route>
>>>> > +      </Routes>
>>>> > +    </Routing>
>>>> > +  </Appenders>
>>>> > +  <Loggers>
>>>> > +    <Root level="error">
>>>> > +      <AppenderRef ref="Routing" />
>>>> > +    </Root>
>>>> > +  </Loggers>
>>>> > +</Configuration>
>>>> > \ No newline at end of file
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>> > new file mode 100644
>>>> > index 0000000..e672aea
>>>> > --- /dev/null
>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>> > @@ -0,0 +1,40 @@
>>>> > +<?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 <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="WARN" name="RoutingTest">
>>>> > +  <Appenders>
>>>> > +    <Routing name="Routing">
>>>> > +      <Routes>
>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>> > +        </Script>
>>>> > +        <Route key="Service1">
>>>> > +          <List name="List1" />
>>>> > +        </Route>
>>>> > +        <Route key="Service2">
>>>> > +          <List name="List2" />
>>>> > +        </Route>
>>>> > +      </Routes>
>>>> > +    </Routing>
>>>> > +  </Appenders>
>>>> > +  <Loggers>
>>>> > +    <Root level="error">
>>>> > +      <AppenderRef ref="Routing" />
>>>> > +    </Root>
>>>> > +  </Loggers>
>>>> > +</Configuration>
>>>> > \ No newline at end of file
>>>> >
>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>>>> > ----------------------------------------------------------------------
>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>> > index 2d3d361..a87d9b7 100644
>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>> >              Appenders it references to allow it to shut down properly.
>>>> > -          </p>
>>>> > +           </p>
>>>> > +           <p>
>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>> > +             and when a route is chosen for an log event.
>>>> > +           </p>
>>>> >           <table>
>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>> >             <tr>
>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>> >               <th>Description</th>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>filter</td>
>>>> > +              <td>Filter</td>
>>>> >               <td>Filter</td>
>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>> >               may be used by using a CompositeFilter.</td>
>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>> >               <td>The name of the Appender.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>rewritePolicy</td>
>>>> > +              <td>RewritePolicy</td>
>>>> >               <td>RewritePolicy</td>
>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > -              <td>routes</td>
>>>> > +              <td>Routes</td>
>>>> >               <td>Routes</td>
>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>> >             </tr>
>>>> >             <tr>
>>>> > +              <td>Script</td>
>>>> > +              <td>Script</td>
>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>> > +                the default Route.</td>
>>>> > +            </tr>
>>>> > +            <tr>
>>>> >               <td>ignoreExceptions</td>
>>>> >               <td>boolean</td>
>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>> >           </table>
>>>> >           <h4>Routes</h4>
>>>> >             <p>
>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>> >               can be configured as the default.
>>>> >             </p>
>>>> >             <p>
>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>> > +              log event and returns the String Route key to use.
>>>> > +            </p>
>>>> > +            <p>
>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>> > +            </p>
>>>> > +            <p>
>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>> >
>>>> >
>>>> 
>>>> 
>>>> 
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>>>> 
>>>> 
>>>> 
>>>> 
>>>> -- 
>>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>>> Spring Batch in Action <http://www.manning.com/templier/>
>>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>>> 
>>> 
>>> 
>>> -- 
>>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>>> Spring Batch in Action <http://www.manning.com/templier/>
>>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>>> Home: http://garygregory.com/ <http://garygregory.com/>
>>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
>> 
>> 
>> 
>> -- 
>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>> Home: http://garygregory.com/ <http://garygregory.com/>
>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Remko Popma <re...@gmail.com>.
If the Binding is shared between threads (and therefore not thread-safe), you could put the LogEvent in a ThreadLocal. 

Sent from my iPhone

> On 2016/09/15, at 6:24, Gary Gregory <ga...@gmail.com> wrote:
> 
> Ralph: Thank you for the guidance on this topic.
> 
> I'll tackle the documentation update tonight. 
> 
> WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that, I can take a look.
> 
> I am wondering in general about the performance difference from an app POV between a plain appender and one appender nested in a RoutingAppender.
> 
> I am also wondering about any concurrency issue passing a LogEvent to a Script. Can a LogEvent be skipped when multiple threads use the same RoutingAppender?
> 
> Gary
> 
>> On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ra...@dslextreme.com> wrote:
>> Sounds good.
>> 
>> When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.
>> 
>> The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven�ft really figured out how to do that.
>> 
>> Ralph
>> 
>>> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com> wrote:
>>> 
>>> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
>>> 
>>> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
>>> 
>>> I'll add LogEvent support next...
>>> 
>>> Gary
>>> 
>>>> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ra...@dslextreme.com> wrote:
>>>> OK - It wasn�ft in there when I looked last night.
>>>> 
>>>> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
>>>> 
>>>> Ralph
>>>> 
>>>>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com> wrote:
>>>>> 
>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ra...@dslextreme.com> wrote:
>>>>>> Gary,
>>>>>> 
>>>>>> RoutingAppender calls routes.getPattern().  Wouldn�ft it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>>>>> 
>>>>> That's what Routes.getPattern() does (see this very commit thread):
>>>>> 
>>>>> @@ -90,12 +155,26 @@ public final class Routes {
>>>>>       * @return the pattern.
>>>>>       */
>>>>>      public String getPattern() {
>>>>> +        if (patternScript != null) {
>>>>> +            final SimpleBindings bindings = new SimpleBindings();
>>>>> +            bindings.put("configuration", configuration);
>>>>> +            bindings.put("statusLogger", LOGGER);
>>>>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>> +            return Objects.toString(object, null);
>>>>> +        }
>>>>>          return pattern;
>>>>>      }
>>>>> 
>>>>> Gary
>>>>>  
>>>>>> 
>>>>>> Ralph
>>>>>> 
>>>>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>>>>> >
>>>>>> > Repository: logging-log4j2
>>>>>> > Updated Branches:
>>>>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>>>>> >
>>>>>> >
>>>>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>>>>>> > in a Routes element.
>>>>>> >
>>>>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>>>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a
>>>>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a
>>>>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a
>>>>>> >
>>>>>> > Branch: refs/heads/master
>>>>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>>>>> > Parents: 3846e2a
>>>>>> > Author: Gary Gregory <gg...@apache.org>
>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>>>>> > Committer: Gary Gregory <gg...@apache.org>
>>>>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>>>>> >
>>>>>> > ----------------------------------------------------------------------
>>>>>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>>>>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>>>>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>>>>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>>>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>>>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>>>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>>>>> > ----------------------------------------------------------------------
>>>>>> >
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > index c95b64a..33fccd7 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>>>>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>>>>>> >
>>>>>> > import java.util.Objects;
>>>>>> >
>>>>>> > +import javax.script.SimpleBindings;
>>>>>> > +
>>>>>> > import org.apache.logging.log4j.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>>>>> > 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.PluginBuilderFactory;
>>>>>> > +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.validation.constraints.Required;
>>>>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>>>>> > import org.apache.logging.log4j.status.StatusLogger;
>>>>>> >
>>>>>> > /**
>>>>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>>>>> >
>>>>>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>>>>>> >
>>>>>> > +        @PluginConfiguration
>>>>>> > +        private Configuration configuration;
>>>>>> > +
>>>>>> >         @PluginAttribute("pattern")
>>>>>> >         private String pattern;
>>>>>> >
>>>>>> > -        @PluginElement("Routes")
>>>>>> > +        @PluginElement("Script")
>>>>>> > +        private AbstractScript patternScript;
>>>>>> > +
>>>>>> > +        @PluginElement("Routes")
>>>>>> > +        @Required
>>>>>> >         private Route[] routes;
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public Routes build() {
>>>>>> >             if (routes == null || routes.length == 0) {
>>>>>> > -                LOGGER.error("No routes configured");
>>>>>> > +                LOGGER.error("No Routes configured.");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new Routes(pattern, routes);
>>>>>> > +            if (patternScript != null && pattern != null) {
>>>>>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>>>>>> > +            }
>>>>>> > +            if (patternScript != null) {
>>>>>> > +                if (configuration == null) {
>>>>>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>>>>>> > +                } else {
>>>>>> > +                    configuration.getScriptManager().addScript(patternScript);
>>>>>> > +                }
>>>>>> > +            }
>>>>>> > +            return new Routes(configuration, patternScript, pattern, routes);
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Configuration getConfiguration() {
>>>>>> > +            return configuration;
>>>>>> >         }
>>>>>> >
>>>>>> >         public String getPattern() {
>>>>>> >             return pattern;
>>>>>> >         }
>>>>>> >
>>>>>> > +        public AbstractScript getPatternScript() {
>>>>>> > +            return patternScript;
>>>>>> > +        }
>>>>>> > +
>>>>>> >         public Route[] getRoutes() {
>>>>>> >             return routes;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>>>>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>>>>>> > +            this.configuration = configuration;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>>>>>> >             this.pattern = pattern;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>>>>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>>>>>> > +            this.patternScript = patternScript;
>>>>>> > +            return this;
>>>>>> > +        }
>>>>>> > +
>>>>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>>>>>> >             this.routes = routes;
>>>>>> >             return this;
>>>>>> >         }
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > +
>>>>>> > +    /**
>>>>>> > +     * Creates the Routes.
>>>>>> > +     * @param pattern The pattern.
>>>>>> > +     * @param routes An array of Route elements.
>>>>>> > +     * @return The Routes container.
>>>>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > +     */
>>>>>> > +    @Deprecated
>>>>>> > +    public static Routes createRoutes(
>>>>>> > +            final String pattern,
>>>>>> > +            final Route... routes) {
>>>>>> > +        if (routes == null || routes.length == 0) {
>>>>>> > +            LOGGER.error("No routes configured");
>>>>>> > +            return null;
>>>>>> > +        }
>>>>>> > +        return new Routes(null, null, pattern, routes);
>>>>>> > +    }
>>>>>> > +
>>>>>> >     @PluginBuilderFactory
>>>>>> >     public static Builder newBuilder() {
>>>>>> >         return new Builder();
>>>>>> >     }
>>>>>> > -
>>>>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>>>>> > -
>>>>>> > +
>>>>>> > +    private final Configuration configuration;
>>>>>> > +
>>>>>> >     private final String pattern;
>>>>>> >
>>>>>> > +    private final AbstractScript patternScript;
>>>>>> > +
>>>>>> >     // TODO Why not make this a Map or add a Map.
>>>>>> >     private final Route[] routes;
>>>>>> >
>>>>>> > -    private Routes(final String pattern, final Route... routes) {
>>>>>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>>>>>> > +        this.configuration = configuration;
>>>>>> > +        this.patternScript = patternScript;
>>>>>> >         this.pattern = pattern;
>>>>>> >         this.routes = routes;
>>>>>> >     }
>>>>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>>>>> >      * @return the pattern.
>>>>>> >      */
>>>>>> >     public String getPattern() {
>>>>>> > +        if (patternScript != null) {
>>>>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>>>>> > +            bindings.put("configuration", configuration);
>>>>>> > +            bindings.put("statusLogger", LOGGER);
>>>>>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>>>>>> > +            return Objects.toString(object, null);
>>>>>> > +        }
>>>>>> >         return pattern;
>>>>>> >     }
>>>>>> >
>>>>>> > -    public Route getRoute(String key) {
>>>>>> > -        for (int i = 0; i < routes.length; i++) {
>>>>>> > -            final Route route = routes[i];
>>>>>> > +    /**
>>>>>> > +     * Gets the optional script that decides which route to pick.
>>>>>> > +     * @return the optional script that decides which route to pick. May be null.
>>>>>> > +     */
>>>>>> > +    public AbstractScript getPatternScript() {
>>>>>> > +        return patternScript;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Route getRoute(final String key) {
>>>>>> > +        for (final Route route : routes) {
>>>>>> >             if (Objects.equals(route.getKey(), key)) {
>>>>>> >                 return route;
>>>>>> >             }
>>>>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>>>>> >
>>>>>> >     }
>>>>>> >
>>>>>> > -    /**
>>>>>> > -     * Creates the Routes.
>>>>>> > -     * @param pattern The pattern.
>>>>>> > -     * @param routes An array of Route elements.
>>>>>> > -     * @return The Routes container.
>>>>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>>>>> > -     */
>>>>>> > -    @Deprecated
>>>>>> > -    public static Routes createRoutes(
>>>>>> > -            final String pattern,
>>>>>> > -            final Route... routes) {
>>>>>> > -        if (routes == null || routes.length == 0) {
>>>>>> > -            LOGGER.error("No routes configured");
>>>>>> > -            return null;
>>>>>> > -        }
>>>>>> > -        return new Routes(pattern, routes);
>>>>>> > -    }
>>>>>> > -
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > index 4471333..78fddbc 100644
>>>>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>>>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >
>>>>>> >         @Override
>>>>>> >         public RoutingAppender build() {
>>>>>> > -            if (getName() == null) {
>>>>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>>>>> > +            final String name = getName();
>>>>>> > +            if (name == null) {
>>>>>> > +                LOGGER.error("No name defined for this RoutingAppender");
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> >             if (routes == null) {
>>>>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>>>>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>>>>>> >                 return null;
>>>>>> >             }
>>>>>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>>>>>> >                     configuration, purgePolicy, defaultRouteScript);
>>>>>> >         }
>>>>>> >
>>>>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >     public void start() {
>>>>>> >         if (defaultRouteScript != null) {
>>>>>> >             if (configuration == null) {
>>>>>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>>>>>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>>>>>> >             } else {
>>>>>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>>>>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>>>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>>>>>> >     public RewritePolicy getRewritePolicy() {
>>>>>> >         return rewritePolicy;
>>>>>> >     }
>>>>>> > +
>>>>>> > +    public Routes getRoutes() {
>>>>>> > +        return routes;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    public Configuration getConfiguration() {
>>>>>> > +        return configuration;
>>>>>> > +    }
>>>>>> > }
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > new file mode 100644
>>>>>> > index 0000000..7d90f6b
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>>>>> > @@ -0,0 +1,130 @@
>>>>>> > +/*
>>>>>> > + * 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.appender.routing;
>>>>>> > +
>>>>>> > +import static org.junit.Assert.assertNotNull;
>>>>>> > +import static org.junit.Assert.assertTrue;
>>>>>> > +
>>>>>> > +import java.util.List;
>>>>>> > +import java.util.Map;
>>>>>> > +
>>>>>> > +import org.apache.logging.log4j.core.LogEvent;
>>>>>> > +import org.apache.logging.log4j.core.Logger;
>>>>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>>>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>>>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>>>>> > +import org.junit.Assert;
>>>>>> > +import org.junit.Rule;
>>>>>> > +import org.junit.Test;
>>>>>> > +import org.junit.runner.RunWith;
>>>>>> > +import org.junit.runners.Parameterized;
>>>>>> > +
>>>>>> > +/**
>>>>>> > + *
>>>>>> > + */
>>>>>> > +@RunWith(Parameterized.class)
>>>>>> > +public class RoutesScriptAppenderTest {
>>>>>> > +
>>>>>> > +    @Parameterized.Parameters(name = "{0}")
>>>>>> > +    public static String[] getParameters() {
>>>>>> > +        return new String[] {
>>>>>> > +                "log4j-routing-routes-script-groovy.xml",
>>>>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Rule
>>>>>> > +    public final LoggerContextRule loggerContextRule;
>>>>>> > +
>>>>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>>>>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private ListAppender getListAppender() {
>>>>>> > +        final String key = "Service2";
>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>>>>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>>>>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>>>>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>>>>>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>>>>>> > +        return listAppender;
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private RoutingAppender getRoutingAppender() {
>>>>>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    private void logAndCheck() {
>>>>>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>>>>>> > +        logger.error("Hello");
>>>>>> > +        final ListAppender listAppender = getListAppender();
>>>>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>>>>> > +        assertNotNull("No events generated", list);
>>>>>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>>>>>> > +        logger.error("World");
>>>>>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test(expected = AssertionError.class)
>>>>>> > +    public void testAppenderAbsence() {
>>>>>> > +        loggerContextRule.getListAppender("List1");
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testListAppenderPresence() {
>>>>>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>>>>>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoPurgePolicy() {
>>>>>> > +        // No PurgePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testNoRewritePolicy() {
>>>>>> > +        // No RewritePolicy in this test
>>>>>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderRoutes() {
>>>>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>>>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>>>>> > +        final Routes routes = routingAppender.getRoutes();
>>>>>> > +        Assert.assertNotNull(routes);
>>>>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>>>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingAppenderPresence() {
>>>>>> > +        getRoutingAppender();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence1() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +
>>>>>> > +    @Test
>>>>>> > +    public void testRoutingPresence2() {
>>>>>> > +        logAndCheck();
>>>>>> > +    }
>>>>>> > +}
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..83121ea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>>>>> > +          if ("OSNameFoo".contains("Foo")) {
>>>>>> > +            return "Service2";
>>>>>> > +          }
>>>>>> > +          return "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>> > new file mode 100644
>>>>>> > index 0000000..e672aea
>>>>>> > --- /dev/null
>>>>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>>>>>> > @@ -0,0 +1,40 @@
>>>>>> > +<?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="WARN" name="RoutingTest">
>>>>>> > +  <Appenders>
>>>>>> > +    <Routing name="Routing">
>>>>>> > +      <Routes>
>>>>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>>>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>>>>> > +        </Script>
>>>>>> > +        <Route key="Service1">
>>>>>> > +          <List name="List1" />
>>>>>> > +        </Route>
>>>>>> > +        <Route key="Service2">
>>>>>> > +          <List name="List2" />
>>>>>> > +        </Route>
>>>>>> > +      </Routes>
>>>>>> > +    </Routing>
>>>>>> > +  </Appenders>
>>>>>> > +  <Loggers>
>>>>>> > +    <Root level="error">
>>>>>> > +      <AppenderRef ref="Routing" />
>>>>>> > +    </Root>
>>>>>> > +  </Loggers>
>>>>>> > +</Configuration>
>>>>>> > \ No newline at end of file
>>>>>> >
>>>>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml
>>>>>> > ----------------------------------------------------------------------
>>>>>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>>>>>> > index 2d3d361..a87d9b7 100644
>>>>>> > --- a/src/site/xdoc/manual/appenders.xml
>>>>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>>>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >              Appender may be an appender previously configured and may be referenced by its name or the
>>>>>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>>>>>> >              Appenders it references to allow it to shut down properly.
>>>>>> > -          </p>
>>>>>> > +           </p>
>>>>>> > +           <p>
>>>>>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>>>>>> > +             and when a route is chosen for an log event.
>>>>>> > +           </p>
>>>>>> >           <table>
>>>>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>>>>> >             <tr>
>>>>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >               <th>Description</th>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>filter</td>
>>>>>> > +              <td>Filter</td>
>>>>>> >               <td>Filter</td>
>>>>>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>>>>>> >               may be used by using a CompositeFilter.</td>
>>>>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >               <td>The name of the Appender.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>rewritePolicy</td>
>>>>>> > +              <td>RewritePolicy</td>
>>>>>> >               <td>RewritePolicy</td>
>>>>>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > -              <td>routes</td>
>>>>>> > +              <td>Routes</td>
>>>>>> >               <td>Routes</td>
>>>>>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>>>>>> >             </tr>
>>>>>> >             <tr>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>Script</td>
>>>>>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>>>>>> > +                the default Route.</td>
>>>>>> > +            </tr>
>>>>>> > +            <tr>
>>>>>> >               <td>ignoreExceptions</td>
>>>>>> >               <td>boolean</td>
>>>>>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>>>>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>>>>>> >           </table>
>>>>>> >           <h4>Routes</h4>
>>>>>> >             <p>
>>>>>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>>>>>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>>>>>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>>>>>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>>>>>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>>>>>> >               can be configured as the default.
>>>>>> >             </p>
>>>>>> >             <p>
>>>>>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>>>>>> > +              log event and returns the String Route key to use.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> > +              You must specify either the pattern attribute or the Script element, but not both.
>>>>>> > +            </p>
>>>>>> > +            <p>
>>>>>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>>>>>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>>>>>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>>>>>> >
>>>>>> >
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> ---------------------------------------------------------------------
>>>>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>>>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>>> 
>>>>> 
>>>>> 
>>>>> -- 
>>>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org 
>>>>> Java Persistence with Hibernate, Second Edition
>>>>> JUnit in Action, Second Edition
>>>>> Spring Batch in Action
>>>>> Blog: http://garygregory.wordpress.com 
>>>>> Home: http://garygregory.com/
>>>>> Tweet! http://twitter.com/GaryGregory
>>> 
>>> 
>>> 
>>> -- 
>>> E-Mail: garydgregory@gmail.com | ggregory@apache.org 
>>> Java Persistence with Hibernate, Second Edition
>>> JUnit in Action, Second Edition
>>> Spring Batch in Action
>>> Blog: http://garygregory.wordpress.com 
>>> Home: http://garygregory.com/
>>> Tweet! http://twitter.com/GaryGregory
> 
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com | ggregory@apache.org 
> Java Persistence with Hibernate, Second Edition
> JUnit in Action, Second Edition
> Spring Batch in Action
> Blog: http://garygregory.wordpress.com 
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
Ralph: Thank you for the guidance on this topic.

I'll tackle the documentation update tonight.

WRT Lookups, that's YAGNI for me ATM. If you have a solution path on that,
I can take a look.

I am wondering in general about the performance difference from an app POV
between a plain appender and one appender nested in a RoutingAppender.

I am also wondering about any concurrency issue passing a LogEvent to a
Script. Can a LogEvent be skipped when multiple threads use the same
RoutingAppender?

Gary

On Wed, Sep 14, 2016 at 12:50 PM, Ralph Goers <ra...@dslextreme.com>
wrote:

> Sounds good.
>
> When documenting that you should make it clear that the Map is shared so
> that every thread is seeing the same Map. Users need to be aware that they
> cannot put things in the map and expect it to only be available for that
> single event.
>
> The last, and probably trickiest part, is going to be making it so
> variables in the Map can be accessed via a Lookup. To be honest, I haven’t
> really figured out how to do that.
>
> Ralph
>
> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com> wrote:
>
> The RoutingAppender Scripts now share a Bindings instance which contains a
> ConcurrentMap keyed under "staticVariables". The Bindings instance is
> tracked as a RoutingAppender and Routes ivar.
>
> I created an abstract superclass for (private) ScriptRunner
> implementations which holds on to the ConcurrentMap. The map can act as a
> set of static/global variables for that script and can be shared through a
> Bindings instance. The private ScriptRunner has new method
> ScriptManager.ScriptRunner.createBindings(). Right now there is no script
> specific data added to the Bindings, but there could be in the future.
>
> I'll add LogEvent support next...
>
> Gary
>
> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ra...@dslextreme.com>
> wrote:
>
>> OK - It wasn’t in there when I looked last night.
>>
>> A couple other things. A ConcurrentMap should be created and passed to
>> the init script and the routing script so that the init script can pass
>> variables to the routing script. Also, the routing script really needs to
>> be passed the logEvent so it can route based on data within it.
>>
>> Ralph
>>
>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com>
>> wrote:
>>
>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com
>> > wrote:
>>
>>> Gary,
>>>
>>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for
>>> the Routes class to execute the script in the call to getPattern and return
>>> the result if there is a script?
>>>
>>
>> That's what Routes.getPattern() does (see this very commit thread):
>>
>> @@ -90,12 +155,26 @@ public final class Routes {
>>       * @return the pattern.
>>       */
>>      public String getPattern() {
>> +        if (patternScript != null) {
>> +            final SimpleBindings bindings = new SimpleBindings();
>> +            bindings.put("configuration", configuration);
>> +            bindings.put("statusLogger", LOGGER);
>> +            final Object object = configuration.getScriptManager
>> ().execute(patternScript.getName(), bindings);
>> +            return Objects.toString(object, null);
>> +        }
>>          return pattern;
>>      }
>>
>> Gary
>>
>>
>>>
>>> Ralph
>>>
>>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>>> >
>>> > Repository: logging-log4j2
>>> > Updated Branches:
>>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>>> >
>>> >
>>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add
>>> Script
>>> > in a Routes element.
>>> >
>>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j
>>> 2/commit/e0f29d9a
>>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>> tree/e0f29d9a
>>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>>> diff/e0f29d9a
>>> >
>>> > Branch: refs/heads/master
>>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>>> > Parents: 3846e2a
>>> > Author: Gary Gregory <gg...@apache.org>
>>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>>> > Committer: Gary Gregory <gg...@apache.org>
>>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>>> >
>>> > ----------------------------------------------------------------------
>>> > .../log4j/core/appender/routing/Routes.java     | 121
>>> ++++++++++++-----
>>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>>> > .../routing/RoutesScriptAppenderTest.java       | 130
>>> +++++++++++++++++++
>>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>>> > ----------------------------------------------------------------------
>>> >
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>> core/appender/routing/Routes.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org
>>> /apache/logging/log4j/core/appender/routing/Routes.java
>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > index c95b64a..33fccd7 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/Routes.java
>>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>>> appender.routing;
>>> >
>>> > import java.util.Objects;
>>> >
>>> > +import javax.script.SimpleBindings;
>>> > +
>>> > import org.apache.logging.log4j.Logger;
>>> > +import org.apache.logging.log4j.core.config.Configuration;
>>> > 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.PluginBuilderFa
>>> ctory;
>>> > +import org.apache.logging.log4j.core.config.plugins.PluginConfigura
>>> tion;
>>> > import org.apache.logging.log4j.core.config.plugins.PluginElement;
>>> > +import org.apache.logging.log4j.core.config.plugins.validation.cons
>>> traints.Required;
>>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>>> > import org.apache.logging.log4j.status.StatusLogger;
>>> >
>>> > /**
>>> > @@ -33,54 +39,113 @@ public final class Routes {
>>> >
>>> >     public static class Builder implements
>>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>>> >
>>> > +        @PluginConfiguration
>>> > +        private Configuration configuration;
>>> > +
>>> >         @PluginAttribute("pattern")
>>> >         private String pattern;
>>> >
>>> > -        @PluginElement("Routes")
>>> > +        @PluginElement("Script")
>>> > +        private AbstractScript patternScript;
>>> > +
>>> > +        @PluginElement("Routes")
>>> > +        @Required
>>> >         private Route[] routes;
>>> >
>>> >         @Override
>>> >         public Routes build() {
>>> >             if (routes == null || routes.length == 0) {
>>> > -                LOGGER.error("No routes configured");
>>> > +                LOGGER.error("No Routes configured.");
>>> >                 return null;
>>> >             }
>>> > -            return new Routes(pattern, routes);
>>> > +            if (patternScript != null && pattern != null) {
>>> > +                LOGGER.warn("In a Routes element, you must configure
>>> either a Script element or a pattern attribute.");
>>> > +            }
>>> > +            if (patternScript != null) {
>>> > +                if (configuration == null) {
>>> > +                    LOGGER.error("No Configuration defined for
>>> Routes; required for Script");
>>> > +                } else {
>>> > +                    configuration.getScriptManager
>>> ().addScript(patternScript);
>>> > +                }
>>> > +            }
>>> > +            return new Routes(configuration, patternScript, pattern,
>>> routes);
>>> > +        }
>>> > +
>>> > +        public Configuration getConfiguration() {
>>> > +            return configuration;
>>> >         }
>>> >
>>> >         public String getPattern() {
>>> >             return pattern;
>>> >         }
>>> >
>>> > +        public AbstractScript getPatternScript() {
>>> > +            return patternScript;
>>> > +        }
>>> > +
>>> >         public Route[] getRoutes() {
>>> >             return routes;
>>> >         }
>>> >
>>> > -        public Builder withPattern(@SuppressWarnings("hiding")
>>> String pattern) {
>>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>>> final Configuration configuration) {
>>> > +            this.configuration = configuration;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withPattern(@SuppressWarnings("hiding") final
>>> String pattern) {
>>> >             this.pattern = pattern;
>>> >             return this;
>>> >         }
>>> >
>>> > -        public Builder withRoutes(@SuppressWarnings("hiding")
>>> Route[] routes) {
>>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>>> final AbstractScript patternScript) {
>>> > +            this.patternScript = patternScript;
>>> > +            return this;
>>> > +        }
>>> > +
>>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final
>>> Route[] routes) {
>>> >             this.routes = routes;
>>> >             return this;
>>> >         }
>>> >
>>> >     }
>>> >
>>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > +
>>> > +    /**
>>> > +     * Creates the Routes.
>>> > +     * @param pattern The pattern.
>>> > +     * @param routes An array of Route elements.
>>> > +     * @return The Routes container.
>>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > +     */
>>> > +    @Deprecated
>>> > +    public static Routes createRoutes(
>>> > +            final String pattern,
>>> > +            final Route... routes) {
>>> > +        if (routes == null || routes.length == 0) {
>>> > +            LOGGER.error("No routes configured");
>>> > +            return null;
>>> > +        }
>>> > +        return new Routes(null, null, pattern, routes);
>>> > +    }
>>> > +
>>> >     @PluginBuilderFactory
>>> >     public static Builder newBuilder() {
>>> >         return new Builder();
>>> >     }
>>> > -
>>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>>> > -
>>> > +
>>> > +    private final Configuration configuration;
>>> > +
>>> >     private final String pattern;
>>> >
>>> > +    private final AbstractScript patternScript;
>>> > +
>>> >     // TODO Why not make this a Map or add a Map.
>>> >     private final Route[] routes;
>>> >
>>> > -    private Routes(final String pattern, final Route... routes) {
>>> > +    private Routes(final Configuration configuration, final
>>> AbstractScript patternScript, final String pattern, final Route... routes) {
>>> > +        this.configuration = configuration;
>>> > +        this.patternScript = patternScript;
>>> >         this.pattern = pattern;
>>> >         this.routes = routes;
>>> >     }
>>> > @@ -90,12 +155,26 @@ public final class Routes {
>>> >      * @return the pattern.
>>> >      */
>>> >     public String getPattern() {
>>> > +        if (patternScript != null) {
>>> > +            final SimpleBindings bindings = new SimpleBindings();
>>> > +            bindings.put("configuration", configuration);
>>> > +            bindings.put("statusLogger", LOGGER);
>>> > +            final Object object = configuration.getScriptManager
>>> ().execute(patternScript.getName(), bindings);
>>> > +            return Objects.toString(object, null);
>>> > +        }
>>> >         return pattern;
>>> >     }
>>> >
>>> > -    public Route getRoute(String key) {
>>> > -        for (int i = 0; i < routes.length; i++) {
>>> > -            final Route route = routes[i];
>>> > +    /**
>>> > +     * Gets the optional script that decides which route to pick.
>>> > +     * @return the optional script that decides which route to pick.
>>> May be null.
>>> > +     */
>>> > +    public AbstractScript getPatternScript() {
>>> > +        return patternScript;
>>> > +    }
>>> > +
>>> > +    public Route getRoute(final String key) {
>>> > +        for (final Route route : routes) {
>>> >             if (Objects.equals(route.getKey(), key)) {
>>> >                 return route;
>>> >             }
>>> > @@ -127,22 +206,4 @@ public final class Routes {
>>> >
>>> >     }
>>> >
>>> > -    /**
>>> > -     * Creates the Routes.
>>> > -     * @param pattern The pattern.
>>> > -     * @param routes An array of Route elements.
>>> > -     * @return The Routes container.
>>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>>> > -     */
>>> > -    @Deprecated
>>> > -    public static Routes createRoutes(
>>> > -            final String pattern,
>>> > -            final Route... routes) {
>>> > -        if (routes == null || routes.length == 0) {
>>> > -            LOGGER.error("No routes configured");
>>> > -            return null;
>>> > -        }
>>> > -        return new Routes(pattern, routes);
>>> > -    }
>>> > -
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
>>> core/appender/routing/RoutingAppender.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/main/java/org
>>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > index 4471333..78fddbc 100644
>>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutingAppender.java
>>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >
>>> >         @Override
>>> >         public RoutingAppender build() {
>>> > -            if (getName() == null) {
>>> > -                LOGGER.error("No name defined for RoutingAppender");
>>> > +            final String name = getName();
>>> > +            if (name == null) {
>>> > +                LOGGER.error("No name defined for this
>>> RoutingAppender");
>>> >                 return null;
>>> >             }
>>> >             if (routes == null) {
>>> > -                LOGGER.error("No routes defined for RoutingAppender");
>>> > +                LOGGER.error("No routes defined for RoutingAppender
>>> {}", name);
>>> >                 return null;
>>> >             }
>>> > -            return new RoutingAppender(getName(), getFilter(),
>>> isIgnoreExceptions(), routes, rewritePolicy,
>>> > +            return new RoutingAppender(name, getFilter(),
>>> isIgnoreExceptions(), routes, rewritePolicy,
>>> >                     configuration, purgePolicy, defaultRouteScript);
>>> >         }
>>> >
>>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >     public void start() {
>>> >         if (defaultRouteScript != null) {
>>> >             if (configuration == null) {
>>> > -                error("No Configuration defined for RoutingAppender;
>>> required for DefaultRouteScript");
>>> > +                error("No Configuration defined for RoutingAppender;
>>> required for Script element.");
>>> >             } else {
>>> >                 configuration.getScriptManage
>>> r().addScript(defaultRouteScript);
>>> >                 final SimpleBindings bindings = new SimpleBindings();
>>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>>> AbstractAppender {
>>> >     public RewritePolicy getRewritePolicy() {
>>> >         return rewritePolicy;
>>> >     }
>>> > +
>>> > +    public Routes getRoutes() {
>>> > +        return routes;
>>> > +    }
>>> > +
>>> > +    public Configuration getConfiguration() {
>>> > +        return configuration;
>>> > +    }
>>> > }
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
>>> core/appender/routing/RoutesScriptAppenderTest.java
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/java/org
>>> /apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutesScriptAppenderTest.java
>>> > new file mode 100644
>>> > index 0000000..7d90f6b
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>>> ender/routing/RoutesScriptAppenderTest.java
>>> > @@ -0,0 +1,130 @@
>>> > +/*
>>> > + * 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.appender.routing;
>>> > +
>>> > +import static org.junit.Assert.assertNotNull;
>>> > +import static org.junit.Assert.assertTrue;
>>> > +
>>> > +import java.util.List;
>>> > +import java.util.Map;
>>> > +
>>> > +import org.apache.logging.log4j.core.LogEvent;
>>> > +import org.apache.logging.log4j.core.Logger;
>>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>>> > +import org.junit.Assert;
>>> > +import org.junit.Rule;
>>> > +import org.junit.Test;
>>> > +import org.junit.runner.RunWith;
>>> > +import org.junit.runners.Parameterized;
>>> > +
>>> > +/**
>>> > + *
>>> > + */
>>> > +@RunWith(Parameterized.class)
>>> > +public class RoutesScriptAppenderTest {
>>> > +
>>> > +    @Parameterized.Parameters(name = "{0}")
>>> > +    public static String[] getParameters() {
>>> > +        return new String[] {
>>> > +                "log4j-routing-routes-script-groovy.xml",
>>> > +                "log4j-routing-routes-script-javascript.xml" };
>>> > +    }
>>> > +
>>> > +    @Rule
>>> > +    public final LoggerContextRule loggerContextRule;
>>> > +
>>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>>> on);
>>> > +    }
>>> > +
>>> > +    private ListAppender getListAppender() {
>>> > +        final String key = "Service2";
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertTrue(routingAppender.isStarted());
>>> > +        final Map<String, AppenderControl> appenders =
>>> routingAppender.getAppenders();
>>> > +        final AppenderControl appenderControl = appenders.get(key);
>>> > +        assertNotNull("No appender control generated for '" + key +
>>> "'; appenders = " + appenders, appenderControl);
>>> > +        final ListAppender listAppender = (ListAppender)
>>> appenderControl.getAppender();
>>> > +        return listAppender;
>>> > +    }
>>> > +
>>> > +    private RoutingAppender getRoutingAppender() {
>>> > +        return loggerContextRule.getRequiredAppender("Routing",
>>> RoutingAppender.class);
>>> > +    }
>>> > +
>>> > +    private void logAndCheck() {
>>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>>> utesScriptAppenderTest.class);
>>> > +        logger.error("Hello");
>>> > +        final ListAppender listAppender = getListAppender();
>>> > +        final List<LogEvent> list = listAppender.getEvents();
>>> > +        assertNotNull("No events generated", list);
>>> > +        assertTrue("Incorrect number of events. Expected 1, got " +
>>> list.size(), list.size() == 1);
>>> > +        logger.error("World");
>>> > +        assertTrue("Incorrect number of events. Expected 2, got " +
>>> list.size(), list.size() == 2);
>>> > +    }
>>> > +
>>> > +    @Test(expected = AssertionError.class)
>>> > +    public void testAppenderAbsence() {
>>> > +        loggerContextRule.getListAppender("List1");
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testListAppenderPresence() {
>>> > +        // No appender until an event is routed, even thought we
>>> initialized the default route on startup.
>>> > +        Assert.assertNull("No appender control generated",
>>> getRoutingAppender().getAppenders().get("Service2"));
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoPurgePolicy() {
>>> > +        // No PurgePolicy in this test
>>> > +        Assert.assertNull("Unexpected PurgePolicy",
>>> getRoutingAppender().getPurgePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testNoRewritePolicy() {
>>> > +        // No RewritePolicy in this test
>>> > +        Assert.assertNull("Unexpected RewritePolicy",
>>> getRoutingAppender().getRewritePolicy());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderRoutes() {
>>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>>> > +        final Routes routes = routingAppender.getRoutes();
>>> > +        Assert.assertNotNull(routes);
>>> > +        Assert.assertNotNull(routes.getPatternScript());
>>> > +        Assert.assertEquals("Service2", routes.getPattern());
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingAppenderPresence() {
>>> > +        getRoutingAppender();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence1() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +
>>> > +    @Test
>>> > +    public void testRoutingPresence2() {
>>> > +        logAndCheck();
>>> > +    }
>>> > +}
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>> -script-groovy.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-groovy.xml
>>> > new file mode 100644
>>> > index 0000000..83121ea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> groovy.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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>>> > +          if ("OSNameFoo".contains("Foo")) {
>>> > +            return "Service2";
>>> > +          }
>>> > +          return "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes
>>> -script-javascript.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/log4j-core/src/test/resource
>>> s/log4j-routing-routes-script-javascript.xml
>>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> javascript.xml
>>> > new file mode 100644
>>> > index 0000000..e672aea
>>> > --- /dev/null
>>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>>> javascript.xml
>>> > @@ -0,0 +1,40 @@
>>> > +<?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="WARN" name="RoutingTest">
>>> > +  <Appenders>
>>> > +    <Routing name="Routing">
>>> > +      <Routes>
>>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>>> > +        </Script>
>>> > +        <Route key="Service1">
>>> > +          <List name="List1" />
>>> > +        </Route>
>>> > +        <Route key="Service2">
>>> > +          <List name="List2" />
>>> > +        </Route>
>>> > +      </Routes>
>>> > +    </Routing>
>>> > +  </Appenders>
>>> > +  <Loggers>
>>> > +    <Root level="error">
>>> > +      <AppenderRef ref="Routing" />
>>> > +    </Root>
>>> > +  </Loggers>
>>> > +</Configuration>
>>> > \ No newline at end of file
>>> >
>>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob
>>> /e0f29d9a/src/site/xdoc/manual/appenders.xml
>>> > ----------------------------------------------------------------------
>>> > diff --git a/src/site/xdoc/manual/appenders.xml
>>> b/src/site/xdoc/manual/appenders.xml
>>> > index 2d3d361..a87d9b7 100644
>>> > --- a/src/site/xdoc/manual/appenders.xml
>>> > +++ b/src/site/xdoc/manual/appenders.xml
>>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >              Appender may be an appender previously configured and may
>>> be referenced by its name or the
>>> >              Appender can be dynamically created as needed. The
>>> RoutingAppender should be configured after any
>>> >              Appenders it references to allow it to shut down properly.
>>> > -          </p>
>>> > +           </p>
>>> > +           <p>
>>> > +             You can also configure a RoutingAppender with scripts:
>>> you can run a script when the appender starts
>>> > +             and when a route is chosen for an log event.
>>> > +           </p>
>>> >           <table>
>>> >             <caption align="top">RoutingAppender Parameters</caption>
>>> >             <tr>
>>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >               <th>Description</th>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>filter</td>
>>> > +              <td>Filter</td>
>>> >               <td>Filter</td>
>>> >               <td>A Filter to determine if the event should be handled
>>> by this Appender. More than one Filter
>>> >               may be used by using a CompositeFilter.</td>
>>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >               <td>The name of the Appender.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>rewritePolicy</td>
>>> > +              <td>RewritePolicy</td>
>>> >               <td>RewritePolicy</td>
>>> >               <td>The RewritePolicy that will manipulate the
>>> LogEvent.</td>
>>> >             </tr>
>>> >             <tr>
>>> > -              <td>routes</td>
>>> > +              <td>Routes</td>
>>> >               <td>Routes</td>
>>> >               <td>Contains one or more Route declarations to identify
>>> the criteria for choosing Appenders.</td>
>>> >             </tr>
>>> >             <tr>
>>> > +              <td>Script</td>
>>> > +              <td>Script</td>
>>> > +              <td>This Script runs when Log4j starts the
>>> RoutingAppender and returns a String Route key to determine
>>> > +                the default Route.</td>
>>> > +            </tr>
>>> > +            <tr>
>>> >               <td>ignoreExceptions</td>
>>> >               <td>boolean</td>
>>> >               <td>The default is <code>true</code>, causing exceptions
>>> encountered while appending events to be
>>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>>> AbstractLogEventWrapperEntity {
>>> >           </table>
>>> >           <h4>Routes</h4>
>>> >             <p>
>>> > -              The Routes element accepts a single, required attribute
>>> named "pattern". The pattern is evaluated
>>> > +              The Routes element accepts a single attribute named
>>> "pattern". The pattern is evaluated
>>> >               against all the registered Lookups and the result is
>>> used to select a Route. Each Route may be
>>> >               configured with a key. If the key matches the result of
>>> evaluating the pattern then that Route
>>> >               will be selected. If no key is specified on a Route then
>>> that Route is the default. Only one Route
>>> >               can be configured as the default.
>>> >             </p>
>>> >             <p>
>>> > +              The Routes element may contain a Script child element.
>>> If specified, the Script is run for each
>>> > +              log event and returns the String Route key to use.
>>> > +            </p>
>>> > +            <p>
>>> > +              You must specify either the pattern attribute or the
>>> Script element, but not both.
>>> > +            </p>
>>> > +            <p>
>>> >               Each Route must reference an Appender. If the Route
>>> contains a ref attribute then the
>>> >               Route will reference an Appender that was defined in the
>>> configuration. If the Route contains an
>>> >               Appender definition then an Appender will be created
>>> within the context of the RoutingAppender and
>>> >
>>> >
>>>
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>>
>>>
>>
>>
>> --
>> E-Mail: garydgregory@gmail.com | ggregory@apache.org
>> <gg...@apache.org>
>> Java Persistence with Hibernate, Second Edition
>> <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com
>> Home: http://garygregory.com/
>> Tweet! http://twitter.com/GaryGregory
>>
>>
>>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>
>
>


-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
Sounds good.

When documenting that you should make it clear that the Map is shared so that every thread is seeing the same Map. Users need to be aware that they cannot put things in the map and expect it to only be available for that single event.

The last, and probably trickiest part, is going to be making it so variables in the Map can be accessed via a Lookup. To be honest, I haven’t really figured out how to do that.

Ralph

> On Sep 14, 2016, at 12:34 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> The RoutingAppender Scripts now share a Bindings instance which contains a ConcurrentMap keyed under "staticVariables". The Bindings instance is tracked as a RoutingAppender and Routes ivar.
> 
> I created an abstract superclass for (private) ScriptRunner implementations which holds on to the ConcurrentMap. The map can act as a set of static/global variables for that script and can be shared through a Bindings instance. The private ScriptRunner has new method ScriptManager.ScriptRunner.createBindings(). Right now there is no script specific data added to the Bindings, but there could be in the future.
> 
> I'll add LogEvent support next...
> 
> Gary
> 
> On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> OK - It wasn’t in there when I looked last night.
> 
> A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.
> 
> Ralph
> 
>> On Sep 13, 2016, at 10:49 PM, Gary Gregory <garydgregory@gmail.com <ma...@gmail.com>> wrote:
>> 
>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
>> Gary,
>> 
>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
>> 
>> That's what Routes.getPattern() does (see this very commit thread):
>> 
>> @@ -90,12 +155,26 @@ public final class Routes {
>>       * @return the pattern.
>>       */
>>      public String getPattern() {
>> +        if (patternScript != null) {
>> +            final SimpleBindings bindings = new SimpleBindings();
>> +            bindings.put("configuration", configuration);
>> +            bindings.put("statusLogger", LOGGER);
>> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>> +            return Objects.toString(object, null);
>> +        }
>>          return pattern;
>>      }
>> 
>> Gary
>>  
>> 
>> Ralph
>> 
>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
>> >
>> > Repository: logging-log4j2
>> > Updated Branches:
>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>> >
>> >
>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>> > in a Routes element.
>> >
>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
>> >
>> > Branch: refs/heads/master
>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>> > Parents: 3846e2a
>> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>> >
>> > ----------------------------------------------------------------------
>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>> > ----------------------------------------------------------------------
>> >
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>> > index c95b64a..33fccd7 100644
>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
>> >
>> > import java.util.Objects;
>> >
>> > +import javax.script.SimpleBindings;
>> > +
>> > import org.apache.logging.log4j.Logger;
>> > +import org.apache.logging.log4j.core.config.Configuration;
>> > 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.PluginBuilderFactory;
>> > +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.validation.constraints.Required;
>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>> > import org.apache.logging.log4j.status.StatusLogger;
>> >
>> > /**
>> > @@ -33,54 +39,113 @@ public final class Routes {
>> >
>> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
>> >
>> > +        @PluginConfiguration
>> > +        private Configuration configuration;
>> > +
>> >         @PluginAttribute("pattern")
>> >         private String pattern;
>> >
>> > -        @PluginElement("Routes")
>> > +        @PluginElement("Script")
>> > +        private AbstractScript patternScript;
>> > +
>> > +        @PluginElement("Routes")
>> > +        @Required
>> >         private Route[] routes;
>> >
>> >         @Override
>> >         public Routes build() {
>> >             if (routes == null || routes.length == 0) {
>> > -                LOGGER.error("No routes configured");
>> > +                LOGGER.error("No Routes configured.");
>> >                 return null;
>> >             }
>> > -            return new Routes(pattern, routes);
>> > +            if (patternScript != null && pattern != null) {
>> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
>> > +            }
>> > +            if (patternScript != null) {
>> > +                if (configuration == null) {
>> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
>> > +                } else {
>> > +                    configuration.getScriptManager().addScript(patternScript);
>> > +                }
>> > +            }
>> > +            return new Routes(configuration, patternScript, pattern, routes);
>> > +        }
>> > +
>> > +        public Configuration getConfiguration() {
>> > +            return configuration;
>> >         }
>> >
>> >         public String getPattern() {
>> >             return pattern;
>> >         }
>> >
>> > +        public AbstractScript getPatternScript() {
>> > +            return patternScript;
>> > +        }
>> > +
>> >         public Route[] getRoutes() {
>> >             return routes;
>> >         }
>> >
>> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
>> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
>> > +            this.configuration = configuration;
>> > +            return this;
>> > +        }
>> > +
>> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>> >             this.pattern = pattern;
>> >             return this;
>> >         }
>> >
>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
>> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
>> > +            this.patternScript = patternScript;
>> > +            return this;
>> > +        }
>> > +
>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>> >             this.routes = routes;
>> >             return this;
>> >         }
>> >
>> >     }
>> >
>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>> > +
>> > +    /**
>> > +     * Creates the Routes.
>> > +     * @param pattern The pattern.
>> > +     * @param routes An array of Route elements.
>> > +     * @return The Routes container.
>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>> > +     */
>> > +    @Deprecated
>> > +    public static Routes createRoutes(
>> > +            final String pattern,
>> > +            final Route... routes) {
>> > +        if (routes == null || routes.length == 0) {
>> > +            LOGGER.error("No routes configured");
>> > +            return null;
>> > +        }
>> > +        return new Routes(null, null, pattern, routes);
>> > +    }
>> > +
>> >     @PluginBuilderFactory
>> >     public static Builder newBuilder() {
>> >         return new Builder();
>> >     }
>> > -
>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>> > -
>> > +
>> > +    private final Configuration configuration;
>> > +
>> >     private final String pattern;
>> >
>> > +    private final AbstractScript patternScript;
>> > +
>> >     // TODO Why not make this a Map or add a Map.
>> >     private final Route[] routes;
>> >
>> > -    private Routes(final String pattern, final Route... routes) {
>> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
>> > +        this.configuration = configuration;
>> > +        this.patternScript = patternScript;
>> >         this.pattern = pattern;
>> >         this.routes = routes;
>> >     }
>> > @@ -90,12 +155,26 @@ public final class Routes {
>> >      * @return the pattern.
>> >      */
>> >     public String getPattern() {
>> > +        if (patternScript != null) {
>> > +            final SimpleBindings bindings = new SimpleBindings();
>> > +            bindings.put("configuration", configuration);
>> > +            bindings.put("statusLogger", LOGGER);
>> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
>> > +            return Objects.toString(object, null);
>> > +        }
>> >         return pattern;
>> >     }
>> >
>> > -    public Route getRoute(String key) {
>> > -        for (int i = 0; i < routes.length; i++) {
>> > -            final Route route = routes[i];
>> > +    /**
>> > +     * Gets the optional script that decides which route to pick.
>> > +     * @return the optional script that decides which route to pick. May be null.
>> > +     */
>> > +    public AbstractScript getPatternScript() {
>> > +        return patternScript;
>> > +    }
>> > +
>> > +    public Route getRoute(final String key) {
>> > +        for (final Route route : routes) {
>> >             if (Objects.equals(route.getKey(), key)) {
>> >                 return route;
>> >             }
>> > @@ -127,22 +206,4 @@ public final class Routes {
>> >
>> >     }
>> >
>> > -    /**
>> > -     * Creates the Routes.
>> > -     * @param pattern The pattern.
>> > -     * @param routes An array of Route elements.
>> > -     * @return The Routes container.
>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>> > -     */
>> > -    @Deprecated
>> > -    public static Routes createRoutes(
>> > -            final String pattern,
>> > -            final Route... routes) {
>> > -        if (routes == null || routes.length == 0) {
>> > -            LOGGER.error("No routes configured");
>> > -            return null;
>> > -        }
>> > -        return new Routes(pattern, routes);
>> > -    }
>> > -
>> > }
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>> > index 4471333..78fddbc 100644
>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
>> >
>> >         @Override
>> >         public RoutingAppender build() {
>> > -            if (getName() == null) {
>> > -                LOGGER.error("No name defined for RoutingAppender");
>> > +            final String name = getName();
>> > +            if (name == null) {
>> > +                LOGGER.error("No name defined for this RoutingAppender");
>> >                 return null;
>> >             }
>> >             if (routes == null) {
>> > -                LOGGER.error("No routes defined for RoutingAppender");
>> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>> >                 return null;
>> >             }
>> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>> >                     configuration, purgePolicy, defaultRouteScript);
>> >         }
>> >
>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>> >     public void start() {
>> >         if (defaultRouteScript != null) {
>> >             if (configuration == null) {
>> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
>> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
>> >             } else {
>> >                 configuration.getScriptManager().addScript(defaultRouteScript);
>> >                 final SimpleBindings bindings = new SimpleBindings();
>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>> >     public RewritePolicy getRewritePolicy() {
>> >         return rewritePolicy;
>> >     }
>> > +
>> > +    public Routes getRoutes() {
>> > +        return routes;
>> > +    }
>> > +
>> > +    public Configuration getConfiguration() {
>> > +        return configuration;
>> > +    }
>> > }
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>> > new file mode 100644
>> > index 0000000..7d90f6b
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
>> > @@ -0,0 +1,130 @@
>> > +/*
>> > + * 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 <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.appender.routing;
>> > +
>> > +import static org.junit.Assert.assertNotNull;
>> > +import static org.junit.Assert.assertTrue;
>> > +
>> > +import java.util.List;
>> > +import java.util.Map;
>> > +
>> > +import org.apache.logging.log4j.core.LogEvent;
>> > +import org.apache.logging.log4j.core.Logger;
>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>> > +import org.junit.Assert;
>> > +import org.junit.Rule;
>> > +import org.junit.Test;
>> > +import org.junit.runner.RunWith;
>> > +import org.junit.runners.Parameterized;
>> > +
>> > +/**
>> > + *
>> > + */
>> > +@RunWith(Parameterized.class)
>> > +public class RoutesScriptAppenderTest {
>> > +
>> > +    @Parameterized.Parameters(name = "{0}")
>> > +    public static String[] getParameters() {
>> > +        return new String[] {
>> > +                "log4j-routing-routes-script-groovy.xml",
>> > +                "log4j-routing-routes-script-javascript.xml" };
>> > +    }
>> > +
>> > +    @Rule
>> > +    public final LoggerContextRule loggerContextRule;
>> > +
>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
>> > +    }
>> > +
>> > +    private ListAppender getListAppender() {
>> > +        final String key = "Service2";
>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>> > +        Assert.assertTrue(routingAppender.isStarted());
>> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
>> > +        final AppenderControl appenderControl = appenders.get(key);
>> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
>> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
>> > +        return listAppender;
>> > +    }
>> > +
>> > +    private RoutingAppender getRoutingAppender() {
>> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
>> > +    }
>> > +
>> > +    private void logAndCheck() {
>> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
>> > +        logger.error("Hello");
>> > +        final ListAppender listAppender = getListAppender();
>> > +        final List<LogEvent> list = listAppender.getEvents();
>> > +        assertNotNull("No events generated", list);
>> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
>> > +        logger.error("World");
>> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
>> > +    }
>> > +
>> > +    @Test(expected = AssertionError.class)
>> > +    public void testAppenderAbsence() {
>> > +        loggerContextRule.getListAppender("List1");
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testListAppenderPresence() {
>> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
>> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testNoPurgePolicy() {
>> > +        // No PurgePolicy in this test
>> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testNoRewritePolicy() {
>> > +        // No RewritePolicy in this test
>> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingAppenderRoutes() {
>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>> > +        final Routes routes = routingAppender.getRoutes();
>> > +        Assert.assertNotNull(routes);
>> > +        Assert.assertNotNull(routes.ge <http://routes.ge/>tPatternScript());
>> > +        Assert.assertEquals("Service2", routes.getPattern());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingAppenderPresence() {
>> > +        getRoutingAppender();
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingPresence1() {
>> > +        logAndCheck();
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingPresence2() {
>> > +        logAndCheck();
>> > +    }
>> > +}
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>> > new file mode 100644
>> > index 0000000..83121ea
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
>> > +  <Appenders>
>> > +    <Routing name="Routing">
>> > +      <Routes>
>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>> > +          if ("OSNameFoo".contains("Foo")) {
>> > +            return "Service2";
>> > +          }
>> > +          return "Service1";]]>
>> > +        </Script>
>> > +        <Route key="Service1">
>> > +          <List name="List1" />
>> > +        </Route>
>> > +        <Route key="Service2">
>> > +          <List name="List2" />
>> > +        </Route>
>> > +      </Routes>
>> > +    </Routing>
>> > +  </Appenders>
>> > +  <Loggers>
>> > +    <Root level="error">
>> > +      <AppenderRef ref="Routing" />
>> > +    </Root>
>> > +  </Loggers>
>> > +</Configuration>
>> > \ No newline at end of file
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>> > new file mode 100644
>> > index 0000000..e672aea
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>> > @@ -0,0 +1,40 @@
>> > +<?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 <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="WARN" name="RoutingTest">
>> > +  <Appenders>
>> > +    <Routing name="Routing">
>> > +      <Routes>
>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>> > +        </Script>
>> > +        <Route key="Service1">
>> > +          <List name="List1" />
>> > +        </Route>
>> > +        <Route key="Service2">
>> > +          <List name="List2" />
>> > +        </Route>
>> > +      </Routes>
>> > +    </Routing>
>> > +  </Appenders>
>> > +  <Loggers>
>> > +    <Root level="error">
>> > +      <AppenderRef ref="Routing" />
>> > +    </Root>
>> > +  </Loggers>
>> > +</Configuration>
>> > \ No newline at end of file
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
>> > ----------------------------------------------------------------------
>> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
>> > index 2d3d361..a87d9b7 100644
>> > --- a/src/site/xdoc/manual/appenders.xml
>> > +++ b/src/site/xdoc/manual/appenders.xml
>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>> >              Appender may be an appender previously configured and may be referenced by its name or the
>> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>> >              Appenders it references to allow it to shut down properly.
>> > -          </p>
>> > +           </p>
>> > +           <p>
>> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
>> > +             and when a route is chosen for an log event.
>> > +           </p>
>> >           <table>
>> >             <caption align="top">RoutingAppender Parameters</caption>
>> >             <tr>
>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>> >               <th>Description</th>
>> >             </tr>
>> >             <tr>
>> > -              <td>filter</td>
>> > +              <td>Filter</td>
>> >               <td>Filter</td>
>> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>> >               may be used by using a CompositeFilter.</td>
>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>> >               <td>The name of the Appender.</td>
>> >             </tr>
>> >             <tr>
>> > -              <td>rewritePolicy</td>
>> > +              <td>RewritePolicy</td>
>> >               <td>RewritePolicy</td>
>> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>> >             </tr>
>> >             <tr>
>> > -              <td>routes</td>
>> > +              <td>Routes</td>
>> >               <td>Routes</td>
>> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>> >             </tr>
>> >             <tr>
>> > +              <td>Script</td>
>> > +              <td>Script</td>
>> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
>> > +                the default Route.</td>
>> > +            </tr>
>> > +            <tr>
>> >               <td>ignoreExceptions</td>
>> >               <td>boolean</td>
>> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>> >           </table>
>> >           <h4>Routes</h4>
>> >             <p>
>> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
>> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
>> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
>> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>> >               can be configured as the default.
>> >             </p>
>> >             <p>
>> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
>> > +              log event and returns the String Route key to use.
>> > +            </p>
>> > +            <p>
>> > +              You must specify either the pattern attribute or the Script element, but not both.
>> > +            </p>
>> > +            <p>
>> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
>> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
>> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
>> >
>> >
>> 
>> 
>> 
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
>> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
>> 
>> 
>> 
>> 
>> -- 
>> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
>> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
>> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
>> Spring Batch in Action <http://www.manning.com/templier/>
>> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
>> Home: http://garygregory.com/ <http://garygregory.com/>
>> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>
> 
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
The RoutingAppender Scripts now share a Bindings instance which contains a
ConcurrentMap keyed under "staticVariables". The Bindings instance is
tracked as a RoutingAppender and Routes ivar.

I created an abstract superclass for (private) ScriptRunner implementations
which holds on to the ConcurrentMap. The map can act as a set of
static/global variables for that script and can be shared through a
Bindings instance. The private ScriptRunner has new method
ScriptManager.ScriptRunner.createBindings(). Right now there is no script
specific data added to the Bindings, but there could be in the future.

I'll add LogEvent support next...

Gary

On Wed, Sep 14, 2016 at 6:42 AM, Ralph Goers <ra...@dslextreme.com>
wrote:

> OK - It wasn’t in there when I looked last night.
>
> A couple other things. A ConcurrentMap should be created and passed to the
> init script and the routing script so that the init script can pass
> variables to the routing script. Also, the routing script really needs to
> be passed the logEvent so it can route based on data within it.
>
> Ralph
>
> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com> wrote:
>
> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ra...@dslextreme.com>
>  wrote:
>
>> Gary,
>>
>> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for
>> the Routes class to execute the script in the call to getPattern and return
>> the result if there is a script?
>>
>
> That's what Routes.getPattern() does (see this very commit thread):
>
> @@ -90,12 +155,26 @@ public final class Routes {
>       * @return the pattern.
>       */
>      public String getPattern() {
> +        if (patternScript != null) {
> +            final SimpleBindings bindings = new SimpleBindings();
> +            bindings.put("configuration", configuration);
> +            bindings.put("statusLogger", LOGGER);
> +            final Object object = configuration.getScriptManager
> ().execute(patternScript.getName(), bindings);
> +            return Objects.toString(object, null);
> +        }
>          return pattern;
>      }
>
> Gary
>
>
>>
>> Ralph
>>
>> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
>> >
>> > Repository: logging-log4j2
>> > Updated Branches:
>> >  refs/heads/master 3846e2a87 -> e0f29d9ad
>> >
>> >
>> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
>> > in a Routes element.
>> >
>> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
>> > Commit: http://git-wip-us.apache.org/repos/asf/logging-
>> log4j2/commit/e0f29d9a
>> > Tree: http://git-wip-us.apache.org/repos/asf/logging-
>> log4j2/tree/e0f29d9a
>> > Diff: http://git-wip-us.apache.org/repos/asf/logging-
>> log4j2/diff/e0f29d9a
>> >
>> > Branch: refs/heads/master
>> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
>> > Parents: 3846e2a
>> > Author: Gary Gregory <gg...@apache.org>
>> > Authored: Tue Sep 13 21:59:59 2016 -0700
>> > Committer: Gary Gregory <gg...@apache.org>
>> > Committed: Tue Sep 13 21:59:59 2016 -0700
>> >
>> > ----------------------------------------------------------------------
>> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
>> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
>> > .../routing/RoutesScriptAppenderTest.java       | 130
>> +++++++++++++++++++
>> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
>> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
>> > src/site/xdoc/manual/appenders.xml              |  27 +++-
>> > 6 files changed, 340 insertions(+), 40 deletions(-)
>> > ----------------------------------------------------------------------
>> >
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/
>> log4j/core/appender/routing/Routes.java
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/Routes.java
>> > index c95b64a..33fccd7 100644
>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/Routes.java
>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/Routes.java
>> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
>> appender.routing;
>> >
>> > import java.util.Objects;
>> >
>> > +import javax.script.SimpleBindings;
>> > +
>> > import org.apache.logging.log4j.Logger;
>> > +import org.apache.logging.log4j.core.config.Configuration;
>> > 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.PluginBuilderFa
>> ctory;
>> > +import org.apache.logging.log4j.core.config.plugins.PluginConfigura
>> tion;
>> > import org.apache.logging.log4j.core.config.plugins.PluginElement;
>> > +import org.apache.logging.log4j.core.config.plugins.validation.cons
>> traints.Required;
>> > +import org.apache.logging.log4j.core.script.AbstractScript;
>> > import org.apache.logging.log4j.status.StatusLogger;
>> >
>> > /**
>> > @@ -33,54 +39,113 @@ public final class Routes {
>> >
>> >     public static class Builder implements
>> org.apache.logging.log4j.core.util.Builder<Routes>  {
>> >
>> > +        @PluginConfiguration
>> > +        private Configuration configuration;
>> > +
>> >         @PluginAttribute("pattern")
>> >         private String pattern;
>> >
>> > -        @PluginElement("Routes")
>> > +        @PluginElement("Script")
>> > +        private AbstractScript patternScript;
>> > +
>> > +        @PluginElement("Routes")
>> > +        @Required
>> >         private Route[] routes;
>> >
>> >         @Override
>> >         public Routes build() {
>> >             if (routes == null || routes.length == 0) {
>> > -                LOGGER.error("No routes configured");
>> > +                LOGGER.error("No Routes configured.");
>> >                 return null;
>> >             }
>> > -            return new Routes(pattern, routes);
>> > +            if (patternScript != null && pattern != null) {
>> > +                LOGGER.warn("In a Routes element, you must configure
>> either a Script element or a pattern attribute.");
>> > +            }
>> > +            if (patternScript != null) {
>> > +                if (configuration == null) {
>> > +                    LOGGER.error("No Configuration defined for Routes;
>> required for Script");
>> > +                } else {
>> > +                    configuration.getScriptManager
>> ().addScript(patternScript);
>> > +                }
>> > +            }
>> > +            return new Routes(configuration, patternScript, pattern,
>> routes);
>> > +        }
>> > +
>> > +        public Configuration getConfiguration() {
>> > +            return configuration;
>> >         }
>> >
>> >         public String getPattern() {
>> >             return pattern;
>> >         }
>> >
>> > +        public AbstractScript getPatternScript() {
>> > +            return patternScript;
>> > +        }
>> > +
>> >         public Route[] getRoutes() {
>> >             return routes;
>> >         }
>> >
>> > -        public Builder withPattern(@SuppressWarnings("hiding") String
>> pattern) {
>> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
>> final Configuration configuration) {
>> > +            this.configuration = configuration;
>> > +            return this;
>> > +        }
>> > +
>> > +        public Builder withPattern(@SuppressWarnings("hiding") final
>> String pattern) {
>> >             this.pattern = pattern;
>> >             return this;
>> >         }
>> >
>> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[]
>> routes) {
>> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
>> final AbstractScript patternScript) {
>> > +            this.patternScript = patternScript;
>> > +            return this;
>> > +        }
>> > +
>> > +        public Builder withRoutes(@SuppressWarnings("hiding") final
>> Route[] routes) {
>> >             this.routes = routes;
>> >             return this;
>> >         }
>> >
>> >     }
>> >
>> > +    private static final Logger LOGGER = StatusLogger.getLogger();
>> > +
>> > +    /**
>> > +     * Creates the Routes.
>> > +     * @param pattern The pattern.
>> > +     * @param routes An array of Route elements.
>> > +     * @return The Routes container.
>> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
>> > +     */
>> > +    @Deprecated
>> > +    public static Routes createRoutes(
>> > +            final String pattern,
>> > +            final Route... routes) {
>> > +        if (routes == null || routes.length == 0) {
>> > +            LOGGER.error("No routes configured");
>> > +            return null;
>> > +        }
>> > +        return new Routes(null, null, pattern, routes);
>> > +    }
>> > +
>> >     @PluginBuilderFactory
>> >     public static Builder newBuilder() {
>> >         return new Builder();
>> >     }
>> > -
>> > -    private static final Logger LOGGER = StatusLogger.getLogger();
>> > -
>> > +
>> > +    private final Configuration configuration;
>> > +
>> >     private final String pattern;
>> >
>> > +    private final AbstractScript patternScript;
>> > +
>> >     // TODO Why not make this a Map or add a Map.
>> >     private final Route[] routes;
>> >
>> > -    private Routes(final String pattern, final Route... routes) {
>> > +    private Routes(final Configuration configuration, final
>> AbstractScript patternScript, final String pattern, final Route... routes) {
>> > +        this.configuration = configuration;
>> > +        this.patternScript = patternScript;
>> >         this.pattern = pattern;
>> >         this.routes = routes;
>> >     }
>> > @@ -90,12 +155,26 @@ public final class Routes {
>> >      * @return the pattern.
>> >      */
>> >     public String getPattern() {
>> > +        if (patternScript != null) {
>> > +            final SimpleBindings bindings = new SimpleBindings();
>> > +            bindings.put("configuration", configuration);
>> > +            bindings.put("statusLogger", LOGGER);
>> > +            final Object object = configuration.getScriptManager
>> ().execute(patternScript.getName(), bindings);
>> > +            return Objects.toString(object, null);
>> > +        }
>> >         return pattern;
>> >     }
>> >
>> > -    public Route getRoute(String key) {
>> > -        for (int i = 0; i < routes.length; i++) {
>> > -            final Route route = routes[i];
>> > +    /**
>> > +     * Gets the optional script that decides which route to pick.
>> > +     * @return the optional script that decides which route to pick.
>> May be null.
>> > +     */
>> > +    public AbstractScript getPatternScript() {
>> > +        return patternScript;
>> > +    }
>> > +
>> > +    public Route getRoute(final String key) {
>> > +        for (final Route route : routes) {
>> >             if (Objects.equals(route.getKey(), key)) {
>> >                 return route;
>> >             }
>> > @@ -127,22 +206,4 @@ public final class Routes {
>> >
>> >     }
>> >
>> > -    /**
>> > -     * Creates the Routes.
>> > -     * @param pattern The pattern.
>> > -     * @param routes An array of Route elements.
>> > -     * @return The Routes container.
>> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
>> > -     */
>> > -    @Deprecated
>> > -    public static Routes createRoutes(
>> > -            final String pattern,
>> > -            final Route... routes) {
>> > -        if (routes == null || routes.length == 0) {
>> > -            LOGGER.error("No routes configured");
>> > -            return null;
>> > -        }
>> > -        return new Routes(pattern, routes);
>> > -    }
>> > -
>> > }
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/
>> log4j/core/appender/routing/RoutingAppender.java
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutingAppender.java b/log4j-core/src/main/java/org
>> /apache/logging/log4j/core/appender/routing/RoutingAppender.java
>> > index 4471333..78fddbc 100644
>> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutingAppender.java
>> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutingAppender.java
>> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
>> AbstractAppender {
>> >
>> >         @Override
>> >         public RoutingAppender build() {
>> > -            if (getName() == null) {
>> > -                LOGGER.error("No name defined for RoutingAppender");
>> > +            final String name = getName();
>> > +            if (name == null) {
>> > +                LOGGER.error("No name defined for this
>> RoutingAppender");
>> >                 return null;
>> >             }
>> >             if (routes == null) {
>> > -                LOGGER.error("No routes defined for RoutingAppender");
>> > +                LOGGER.error("No routes defined for RoutingAppender
>> {}", name);
>> >                 return null;
>> >             }
>> > -            return new RoutingAppender(getName(), getFilter(),
>> isIgnoreExceptions(), routes, rewritePolicy,
>> > +            return new RoutingAppender(name, getFilter(),
>> isIgnoreExceptions(), routes, rewritePolicy,
>> >                     configuration, purgePolicy, defaultRouteScript);
>> >         }
>> >
>> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
>> AbstractAppender {
>> >     public void start() {
>> >         if (defaultRouteScript != null) {
>> >             if (configuration == null) {
>> > -                error("No Configuration defined for RoutingAppender;
>> required for DefaultRouteScript");
>> > +                error("No Configuration defined for RoutingAppender;
>> required for Script element.");
>> >             } else {
>> >                 configuration.getScriptManage
>> r().addScript(defaultRouteScript);
>> >                 final SimpleBindings bindings = new SimpleBindings();
>> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
>> AbstractAppender {
>> >     public RewritePolicy getRewritePolicy() {
>> >         return rewritePolicy;
>> >     }
>> > +
>> > +    public Routes getRoutes() {
>> > +        return routes;
>> > +    }
>> > +
>> > +    public Configuration getConfiguration() {
>> > +        return configuration;
>> > +    }
>> > }
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/
>> log4j/core/appender/routing/RoutesScriptAppenderTest.java
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutesScriptAppenderTest.java
>> b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutesScriptAppenderTest.java
>> > new file mode 100644
>> > index 0000000..7d90f6b
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/app
>> ender/routing/RoutesScriptAppenderTest.java
>> > @@ -0,0 +1,130 @@
>> > +/*
>> > + * 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.appender.routing;
>> > +
>> > +import static org.junit.Assert.assertNotNull;
>> > +import static org.junit.Assert.assertTrue;
>> > +
>> > +import java.util.List;
>> > +import java.util.Map;
>> > +
>> > +import org.apache.logging.log4j.core.LogEvent;
>> > +import org.apache.logging.log4j.core.Logger;
>> > +import org.apache.logging.log4j.core.config.AppenderControl;
>> > +import org.apache.logging.log4j.junit.LoggerContextRule;
>> > +import org.apache.logging.log4j.test.appender.ListAppender;
>> > +import org.junit.Assert;
>> > +import org.junit.Rule;
>> > +import org.junit.Test;
>> > +import org.junit.runner.RunWith;
>> > +import org.junit.runners.Parameterized;
>> > +
>> > +/**
>> > + *
>> > + */
>> > +@RunWith(Parameterized.class)
>> > +public class RoutesScriptAppenderTest {
>> > +
>> > +    @Parameterized.Parameters(name = "{0}")
>> > +    public static String[] getParameters() {
>> > +        return new String[] {
>> > +                "log4j-routing-routes-script-groovy.xml",
>> > +                "log4j-routing-routes-script-javascript.xml" };
>> > +    }
>> > +
>> > +    @Rule
>> > +    public final LoggerContextRule loggerContextRule;
>> > +
>> > +    public RoutesScriptAppenderTest(final String configLocation) {
>> > +        this.loggerContextRule = new LoggerContextRule(configLocati
>> on);
>> > +    }
>> > +
>> > +    private ListAppender getListAppender() {
>> > +        final String key = "Service2";
>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>> > +        Assert.assertTrue(routingAppender.isStarted());
>> > +        final Map<String, AppenderControl> appenders =
>> routingAppender.getAppenders();
>> > +        final AppenderControl appenderControl = appenders.get(key);
>> > +        assertNotNull("No appender control generated for '" + key +
>> "'; appenders = " + appenders, appenderControl);
>> > +        final ListAppender listAppender = (ListAppender)
>> appenderControl.getAppender();
>> > +        return listAppender;
>> > +    }
>> > +
>> > +    private RoutingAppender getRoutingAppender() {
>> > +        return loggerContextRule.getRequiredAppender("Routing",
>> RoutingAppender.class);
>> > +    }
>> > +
>> > +    private void logAndCheck() {
>> > +        final Logger logger = loggerContextRule.getLogger(Ro
>> utesScriptAppenderTest.class);
>> > +        logger.error("Hello");
>> > +        final ListAppender listAppender = getListAppender();
>> > +        final List<LogEvent> list = listAppender.getEvents();
>> > +        assertNotNull("No events generated", list);
>> > +        assertTrue("Incorrect number of events. Expected 1, got " +
>> list.size(), list.size() == 1);
>> > +        logger.error("World");
>> > +        assertTrue("Incorrect number of events. Expected 2, got " +
>> list.size(), list.size() == 2);
>> > +    }
>> > +
>> > +    @Test(expected = AssertionError.class)
>> > +    public void testAppenderAbsence() {
>> > +        loggerContextRule.getListAppender("List1");
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testListAppenderPresence() {
>> > +        // No appender until an event is routed, even thought we
>> initialized the default route on startup.
>> > +        Assert.assertNull("No appender control generated",
>> getRoutingAppender().getAppenders().get("Service2"));
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testNoPurgePolicy() {
>> > +        // No PurgePolicy in this test
>> > +        Assert.assertNull("Unexpected PurgePolicy",
>> getRoutingAppender().getPurgePolicy());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testNoRewritePolicy() {
>> > +        // No RewritePolicy in this test
>> > +        Assert.assertNull("Unexpected RewritePolicy",
>> getRoutingAppender().getRewritePolicy());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingAppenderRoutes() {
>> > +        final RoutingAppender routingAppender = getRoutingAppender();
>> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
>> > +        Assert.assertNull(routingAppender.getDefaultRoute());
>> > +        final Routes routes = routingAppender.getRoutes();
>> > +        Assert.assertNotNull(routes);
>> > +        Assert.assertNotNull(routes.getPatternScript());
>> > +        Assert.assertEquals("Service2", routes.getPattern());
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingAppenderPresence() {
>> > +        getRoutingAppender();
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingPresence1() {
>> > +        logAndCheck();
>> > +    }
>> > +
>> > +    @Test
>> > +    public void testRoutingPresence2() {
>> > +        logAndCheck();
>> > +    }
>> > +}
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-r
>> outes-script-groovy.xml
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>> b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
>> > new file mode 100644
>> > index 0000000..83121ea
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>> groovy.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="WARN" name="RoutingTest">
>> > +  <Appenders>
>> > +    <Routing name="Routing">
>> > +      <Routes>
>> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
>> > +          if ("OSNameFoo".contains("Foo")) {
>> > +            return "Service2";
>> > +          }
>> > +          return "Service1";]]>
>> > +        </Script>
>> > +        <Route key="Service1">
>> > +          <List name="List1" />
>> > +        </Route>
>> > +        <Route key="Service2">
>> > +          <List name="List2" />
>> > +        </Route>
>> > +      </Routes>
>> > +    </Routing>
>> > +  </Appenders>
>> > +  <Loggers>
>> > +    <Root level="error">
>> > +      <AppenderRef ref="Routing" />
>> > +    </Root>
>> > +  </Loggers>
>> > +</Configuration>
>> > \ No newline at end of file
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-r
>> outes-script-javascript.xml
>> > ----------------------------------------------------------------------
>> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
>> b/log4j-core/src/test/resources/log4j-routing-routes-script-
>> javascript.xml
>> > new file mode 100644
>> > index 0000000..e672aea
>> > --- /dev/null
>> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-
>> javascript.xml
>> > @@ -0,0 +1,40 @@
>> > +<?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="WARN" name="RoutingTest">
>> > +  <Appenders>
>> > +    <Routing name="Routing">
>> > +      <Routes>
>> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
>> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
>> > +        </Script>
>> > +        <Route key="Service1">
>> > +          <List name="List1" />
>> > +        </Route>
>> > +        <Route key="Service2">
>> > +          <List name="List2" />
>> > +        </Route>
>> > +      </Routes>
>> > +    </Routing>
>> > +  </Appenders>
>> > +  <Loggers>
>> > +    <Root level="error">
>> > +      <AppenderRef ref="Routing" />
>> > +    </Root>
>> > +  </Loggers>
>> > +</Configuration>
>> > \ No newline at end of file
>> >
>> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/
>> blob/e0f29d9a/src/site/xdoc/manual/appenders.xml
>> > ----------------------------------------------------------------------
>> > diff --git a/src/site/xdoc/manual/appenders.xml
>> b/src/site/xdoc/manual/appenders.xml
>> > index 2d3d361..a87d9b7 100644
>> > --- a/src/site/xdoc/manual/appenders.xml
>> > +++ b/src/site/xdoc/manual/appenders.xml
>> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
>> AbstractLogEventWrapperEntity {
>> >              Appender may be an appender previously configured and may
>> be referenced by its name or the
>> >              Appender can be dynamically created as needed. The
>> RoutingAppender should be configured after any
>> >              Appenders it references to allow it to shut down properly.
>> > -          </p>
>> > +           </p>
>> > +           <p>
>> > +             You can also configure a RoutingAppender with scripts:
>> you can run a script when the appender starts
>> > +             and when a route is chosen for an log event.
>> > +           </p>
>> >           <table>
>> >             <caption align="top">RoutingAppender Parameters</caption>
>> >             <tr>
>> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
>> AbstractLogEventWrapperEntity {
>> >               <th>Description</th>
>> >             </tr>
>> >             <tr>
>> > -              <td>filter</td>
>> > +              <td>Filter</td>
>> >               <td>Filter</td>
>> >               <td>A Filter to determine if the event should be handled
>> by this Appender. More than one Filter
>> >               may be used by using a CompositeFilter.</td>
>> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
>> AbstractLogEventWrapperEntity {
>> >               <td>The name of the Appender.</td>
>> >             </tr>
>> >             <tr>
>> > -              <td>rewritePolicy</td>
>> > +              <td>RewritePolicy</td>
>> >               <td>RewritePolicy</td>
>> >               <td>The RewritePolicy that will manipulate the
>> LogEvent.</td>
>> >             </tr>
>> >             <tr>
>> > -              <td>routes</td>
>> > +              <td>Routes</td>
>> >               <td>Routes</td>
>> >               <td>Contains one or more Route declarations to identify
>> the criteria for choosing Appenders.</td>
>> >             </tr>
>> >             <tr>
>> > +              <td>Script</td>
>> > +              <td>Script</td>
>> > +              <td>This Script runs when Log4j starts the
>> RoutingAppender and returns a String Route key to determine
>> > +                the default Route.</td>
>> > +            </tr>
>> > +            <tr>
>> >               <td>ignoreExceptions</td>
>> >               <td>boolean</td>
>> >               <td>The default is <code>true</code>, causing exceptions
>> encountered while appending events to be
>> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
>> AbstractLogEventWrapperEntity {
>> >           </table>
>> >           <h4>Routes</h4>
>> >             <p>
>> > -              The Routes element accepts a single, required attribute
>> named "pattern". The pattern is evaluated
>> > +              The Routes element accepts a single attribute named
>> "pattern". The pattern is evaluated
>> >               against all the registered Lookups and the result is used
>> to select a Route. Each Route may be
>> >               configured with a key. If the key matches the result of
>> evaluating the pattern then that Route
>> >               will be selected. If no key is specified on a Route then
>> that Route is the default. Only one Route
>> >               can be configured as the default.
>> >             </p>
>> >             <p>
>> > +              The Routes element may contain a Script child element.
>> If specified, the Script is run for each
>> > +              log event and returns the String Route key to use.
>> > +            </p>
>> > +            <p>
>> > +              You must specify either the pattern attribute or the
>> Script element, but not both.
>> > +            </p>
>> > +            <p>
>> >               Each Route must reference an Appender. If the Route
>> contains a ref attribute then the
>> >               Route will reference an Appender that was defined in the
>> configuration. If the Route contains an
>> >               Appender definition then an Appender will be created
>> within the context of the RoutingAppender and
>> >
>> >
>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
>> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>>
>>
>
>
> --
> E-Mail: garydgregory@gmail.com | ggregory@apache.org
> <gg...@apache.org>
> Java Persistence with Hibernate, Second Edition
> <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com
> Home: http://garygregory.com/
> Tweet! http://twitter.com/GaryGregory
>
>
>


-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
OK - It wasn’t in there when I looked last night.

A couple other things. A ConcurrentMap should be created and passed to the init script and the routing script so that the init script can pass variables to the routing script. Also, the routing script really needs to be passed the logEvent so it can route based on data within it.

Ralph

> On Sep 13, 2016, at 10:49 PM, Gary Gregory <ga...@gmail.com> wrote:
> 
> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ralph.goers@dslextreme.com <ma...@dslextreme.com>> wrote:
> Gary,
> 
> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?
> 
> That's what Routes.getPattern() does (see this very commit thread):
> 
> @@ -90,12 +155,26 @@ public final class Routes {
>       * @return the pattern.
>       */
>      public String getPattern() {
> +        if (patternScript != null) {
> +            final SimpleBindings bindings = new SimpleBindings();
> +            bindings.put("configuration", configuration);
> +            bindings.put("statusLogger", LOGGER);
> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
> +            return Objects.toString(object, null);
> +        }
>          return pattern;
>      }
> 
> Gary
>  
> 
> Ralph
> 
> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org <ma...@apache.org> wrote:
> >
> > Repository: logging-log4j2
> > Updated Branches:
> >  refs/heads/master 3846e2a87 -> e0f29d9ad
> >
> >
> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
> > in a Routes element.
> >
> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo <http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo>
> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a>
> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a>
> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a <http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a>
> >
> > Branch: refs/heads/master
> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
> > Parents: 3846e2a
> > Author: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
> > Authored: Tue Sep 13 21:59:59 2016 -0700
> > Committer: Gary Gregory <ggregory@apache.org <ma...@apache.org>>
> > Committed: Tue Sep 13 21:59:59 2016 -0700
> >
> > ----------------------------------------------------------------------
> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
> > .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
> > src/site/xdoc/manual/appenders.xml              |  27 +++-
> > 6 files changed, 340 insertions(+), 40 deletions(-)
> > ----------------------------------------------------------------------
> >
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java>
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> > index c95b64a..33fccd7 100644
> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
> >
> > import java.util.Objects;
> >
> > +import javax.script.SimpleBindings;
> > +
> > import org.apache.logging.log4j.Logger;
> > +import org.apache.logging.log4j.core.config.Configuration;
> > 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.PluginBuilderFactory;
> > +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.validation.constraints.Required;
> > +import org.apache.logging.log4j.core.script.AbstractScript;
> > import org.apache.logging.log4j.status.StatusLogger;
> >
> > /**
> > @@ -33,54 +39,113 @@ public final class Routes {
> >
> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
> >
> > +        @PluginConfiguration
> > +        private Configuration configuration;
> > +
> >         @PluginAttribute("pattern")
> >         private String pattern;
> >
> > -        @PluginElement("Routes")
> > +        @PluginElement("Script")
> > +        private AbstractScript patternScript;
> > +
> > +        @PluginElement("Routes")
> > +        @Required
> >         private Route[] routes;
> >
> >         @Override
> >         public Routes build() {
> >             if (routes == null || routes.length == 0) {
> > -                LOGGER.error("No routes configured");
> > +                LOGGER.error("No Routes configured.");
> >                 return null;
> >             }
> > -            return new Routes(pattern, routes);
> > +            if (patternScript != null && pattern != null) {
> > +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
> > +            }
> > +            if (patternScript != null) {
> > +                if (configuration == null) {
> > +                    LOGGER.error("No Configuration defined for Routes; required for Script");
> > +                } else {
> > +                    configuration.getScriptManager().addScript(patternScript);
> > +                }
> > +            }
> > +            return new Routes(configuration, patternScript, pattern, routes);
> > +        }
> > +
> > +        public Configuration getConfiguration() {
> > +            return configuration;
> >         }
> >
> >         public String getPattern() {
> >             return pattern;
> >         }
> >
> > +        public AbstractScript getPatternScript() {
> > +            return patternScript;
> > +        }
> > +
> >         public Route[] getRoutes() {
> >             return routes;
> >         }
> >
> > -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
> > +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
> > +            this.configuration = configuration;
> > +            return this;
> > +        }
> > +
> > +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
> >             this.pattern = pattern;
> >             return this;
> >         }
> >
> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
> > +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
> > +            this.patternScript = patternScript;
> > +            return this;
> > +        }
> > +
> > +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
> >             this.routes = routes;
> >             return this;
> >         }
> >
> >     }
> >
> > +    private static final Logger LOGGER = StatusLogger.getLogger();
> > +
> > +    /**
> > +     * Creates the Routes.
> > +     * @param pattern The pattern.
> > +     * @param routes An array of Route elements.
> > +     * @return The Routes container.
> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
> > +     */
> > +    @Deprecated
> > +    public static Routes createRoutes(
> > +            final String pattern,
> > +            final Route... routes) {
> > +        if (routes == null || routes.length == 0) {
> > +            LOGGER.error("No routes configured");
> > +            return null;
> > +        }
> > +        return new Routes(null, null, pattern, routes);
> > +    }
> > +
> >     @PluginBuilderFactory
> >     public static Builder newBuilder() {
> >         return new Builder();
> >     }
> > -
> > -    private static final Logger LOGGER = StatusLogger.getLogger();
> > -
> > +
> > +    private final Configuration configuration;
> > +
> >     private final String pattern;
> >
> > +    private final AbstractScript patternScript;
> > +
> >     // TODO Why not make this a Map or add a Map.
> >     private final Route[] routes;
> >
> > -    private Routes(final String pattern, final Route... routes) {
> > +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
> > +        this.configuration = configuration;
> > +        this.patternScript = patternScript;
> >         this.pattern = pattern;
> >         this.routes = routes;
> >     }
> > @@ -90,12 +155,26 @@ public final class Routes {
> >      * @return the pattern.
> >      */
> >     public String getPattern() {
> > +        if (patternScript != null) {
> > +            final SimpleBindings bindings = new SimpleBindings();
> > +            bindings.put("configuration", configuration);
> > +            bindings.put("statusLogger", LOGGER);
> > +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
> > +            return Objects.toString(object, null);
> > +        }
> >         return pattern;
> >     }
> >
> > -    public Route getRoute(String key) {
> > -        for (int i = 0; i < routes.length; i++) {
> > -            final Route route = routes[i];
> > +    /**
> > +     * Gets the optional script that decides which route to pick.
> > +     * @return the optional script that decides which route to pick. May be null.
> > +     */
> > +    public AbstractScript getPatternScript() {
> > +        return patternScript;
> > +    }
> > +
> > +    public Route getRoute(final String key) {
> > +        for (final Route route : routes) {
> >             if (Objects.equals(route.getKey(), key)) {
> >                 return route;
> >             }
> > @@ -127,22 +206,4 @@ public final class Routes {
> >
> >     }
> >
> > -    /**
> > -     * Creates the Routes.
> > -     * @param pattern The pattern.
> > -     * @param routes An array of Route elements.
> > -     * @return The Routes container.
> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
> > -     */
> > -    @Deprecated
> > -    public static Routes createRoutes(
> > -            final String pattern,
> > -            final Route... routes) {
> > -        if (routes == null || routes.length == 0) {
> > -            LOGGER.error("No routes configured");
> > -            return null;
> > -        }
> > -        return new Routes(pattern, routes);
> > -    }
> > -
> > }
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java>
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> > index 4471333..78fddbc 100644
> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
> >
> >         @Override
> >         public RoutingAppender build() {
> > -            if (getName() == null) {
> > -                LOGGER.error("No name defined for RoutingAppender");
> > +            final String name = getName();
> > +            if (name == null) {
> > +                LOGGER.error("No name defined for this RoutingAppender");
> >                 return null;
> >             }
> >             if (routes == null) {
> > -                LOGGER.error("No routes defined for RoutingAppender");
> > +                LOGGER.error("No routes defined for RoutingAppender {}", name);
> >                 return null;
> >             }
> > -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
> > +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
> >                     configuration, purgePolicy, defaultRouteScript);
> >         }
> >
> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
> >     public void start() {
> >         if (defaultRouteScript != null) {
> >             if (configuration == null) {
> > -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
> > +                error("No Configuration defined for RoutingAppender; required for Script element.");
> >             } else {
> >                 configuration.getScriptManager().addScript(defaultRouteScript);
> >                 final SimpleBindings bindings = new SimpleBindings();
> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
> >     public RewritePolicy getRewritePolicy() {
> >         return rewritePolicy;
> >     }
> > +
> > +    public Routes getRoutes() {
> > +        return routes;
> > +    }
> > +
> > +    public Configuration getConfiguration() {
> > +        return configuration;
> > +    }
> > }
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java>
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
> > new file mode 100644
> > index 0000000..7d90f6b
> > --- /dev/null
> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
> > @@ -0,0 +1,130 @@
> > +/*
> > + * 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 <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.appender.routing;
> > +
> > +import static org.junit.Assert.assertNotNull;
> > +import static org.junit.Assert.assertTrue;
> > +
> > +import java.util.List;
> > +import java.util.Map;
> > +
> > +import org.apache.logging.log4j.core.LogEvent;
> > +import org.apache.logging.log4j.core.Logger;
> > +import org.apache.logging.log4j.core.config.AppenderControl;
> > +import org.apache.logging.log4j.junit.LoggerContextRule;
> > +import org.apache.logging.log4j.test.appender.ListAppender;
> > +import org.junit.Assert;
> > +import org.junit.Rule;
> > +import org.junit.Test;
> > +import org.junit.runner.RunWith;
> > +import org.junit.runners.Parameterized;
> > +
> > +/**
> > + *
> > + */
> > +@RunWith(Parameterized.class)
> > +public class RoutesScriptAppenderTest {
> > +
> > +    @Parameterized.Parameters(name = "{0}")
> > +    public static String[] getParameters() {
> > +        return new String[] {
> > +                "log4j-routing-routes-script-groovy.xml",
> > +                "log4j-routing-routes-script-javascript.xml" };
> > +    }
> > +
> > +    @Rule
> > +    public final LoggerContextRule loggerContextRule;
> > +
> > +    public RoutesScriptAppenderTest(final String configLocation) {
> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
> > +    }
> > +
> > +    private ListAppender getListAppender() {
> > +        final String key = "Service2";
> > +        final RoutingAppender routingAppender = getRoutingAppender();
> > +        Assert.assertTrue(routingAppender.isStarted());
> > +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
> > +        final AppenderControl appenderControl = appenders.get(key);
> > +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
> > +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
> > +        return listAppender;
> > +    }
> > +
> > +    private RoutingAppender getRoutingAppender() {
> > +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
> > +    }
> > +
> > +    private void logAndCheck() {
> > +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
> > +        logger.error("Hello");
> > +        final ListAppender listAppender = getListAppender();
> > +        final List<LogEvent> list = listAppender.getEvents();
> > +        assertNotNull("No events generated", list);
> > +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
> > +        logger.error("World");
> > +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
> > +    }
> > +
> > +    @Test(expected = AssertionError.class)
> > +    public void testAppenderAbsence() {
> > +        loggerContextRule.getListAppender("List1");
> > +    }
> > +
> > +    @Test
> > +    public void testListAppenderPresence() {
> > +        // No appender until an event is routed, even thought we initialized the default route on startup.
> > +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
> > +    }
> > +
> > +    @Test
> > +    public void testNoPurgePolicy() {
> > +        // No PurgePolicy in this test
> > +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
> > +    }
> > +
> > +    @Test
> > +    public void testNoRewritePolicy() {
> > +        // No RewritePolicy in this test
> > +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingAppenderRoutes() {
> > +        final RoutingAppender routingAppender = getRoutingAppender();
> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
> > +        Assert.assertNull(routingAppender.getDefaultRoute());
> > +        final Routes routes = routingAppender.getRoutes();
> > +        Assert.assertNotNull(routes);
> > +        Assert.assertNotNull(routes.getPatternScript());
> > +        Assert.assertEquals("Service2", routes.getPattern());
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingAppenderPresence() {
> > +        getRoutingAppender();
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingPresence1() {
> > +        logAndCheck();
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingPresence2() {
> > +        logAndCheck();
> > +    }
> > +}
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml>
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
> > new file mode 100644
> > index 0000000..83121ea
> > --- /dev/null
> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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 <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="WARN" name="RoutingTest">
> > +  <Appenders>
> > +    <Routing name="Routing">
> > +      <Routes>
> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
> > +          if ("OSNameFoo".contains("Foo")) {
> > +            return "Service2";
> > +          }
> > +          return "Service1";]]>
> > +        </Script>
> > +        <Route key="Service1">
> > +          <List name="List1" />
> > +        </Route>
> > +        <Route key="Service2">
> > +          <List name="List2" />
> > +        </Route>
> > +      </Routes>
> > +    </Routing>
> > +  </Appenders>
> > +  <Loggers>
> > +    <Root level="error">
> > +      <AppenderRef ref="Routing" />
> > +    </Root>
> > +  </Loggers>
> > +</Configuration>
> > \ No newline at end of file
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml>
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> > new file mode 100644
> > index 0000000..e672aea
> > --- /dev/null
> > +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> > @@ -0,0 +1,40 @@
> > +<?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 <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="WARN" name="RoutingTest">
> > +  <Appenders>
> > +    <Routing name="Routing">
> > +      <Routes>
> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
> > +        </Script>
> > +        <Route key="Service1">
> > +          <List name="List1" />
> > +        </Route>
> > +        <Route key="Service2">
> > +          <List name="List2" />
> > +        </Route>
> > +      </Routes>
> > +    </Routing>
> > +  </Appenders>
> > +  <Loggers>
> > +    <Root level="error">
> > +      <AppenderRef ref="Routing" />
> > +    </Root>
> > +  </Loggers>
> > +</Configuration>
> > \ No newline at end of file
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml <http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml>
> > ----------------------------------------------------------------------
> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
> > index 2d3d361..a87d9b7 100644
> > --- a/src/site/xdoc/manual/appenders.xml
> > +++ b/src/site/xdoc/manual/appenders.xml
> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
> >              Appender may be an appender previously configured and may be referenced by its name or the
> >              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
> >              Appenders it references to allow it to shut down properly.
> > -          </p>
> > +           </p>
> > +           <p>
> > +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts
> > +             and when a route is chosen for an log event.
> > +           </p>
> >           <table>
> >             <caption align="top">RoutingAppender Parameters</caption>
> >             <tr>
> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
> >               <th>Description</th>
> >             </tr>
> >             <tr>
> > -              <td>filter</td>
> > +              <td>Filter</td>
> >               <td>Filter</td>
> >               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
> >               may be used by using a CompositeFilter.</td>
> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
> >               <td>The name of the Appender.</td>
> >             </tr>
> >             <tr>
> > -              <td>rewritePolicy</td>
> > +              <td>RewritePolicy</td>
> >               <td>RewritePolicy</td>
> >               <td>The RewritePolicy that will manipulate the LogEvent.</td>
> >             </tr>
> >             <tr>
> > -              <td>routes</td>
> > +              <td>Routes</td>
> >               <td>Routes</td>
> >               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
> >             </tr>
> >             <tr>
> > +              <td>Script</td>
> > +              <td>Script</td>
> > +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine
> > +                the default Route.</td>
> > +            </tr>
> > +            <tr>
> >               <td>ignoreExceptions</td>
> >               <td>boolean</td>
> >               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
> >           </table>
> >           <h4>Routes</h4>
> >             <p>
> > -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
> > +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
> >               against all the registered Lookups and the result is used to select a Route. Each Route may be
> >               configured with a key. If the key matches the result of evaluating the pattern then that Route
> >               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
> >               can be configured as the default.
> >             </p>
> >             <p>
> > +              The Routes element may contain a Script child element. If specified, the Script is run for each
> > +              log event and returns the String Route key to use.
> > +            </p>
> > +            <p>
> > +              You must specify either the pattern attribute or the Script element, but not both.
> > +            </p>
> > +            <p>
> >               Each Route must reference an Appender. If the Route contains a ref attribute then the
> >               Route will reference an Appender that was defined in the configuration. If the Route contains an
> >               Appender definition then an Appender will be created within the context of the RoutingAppender and
> >
> >
> 
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org <ma...@logging.apache.org>
> For additional commands, e-mail: log4j-dev-help@logging.apache.org <ma...@logging.apache.org>
> 
> 
> 
> 
> -- 
> E-Mail: garydgregory@gmail.com <ma...@gmail.com> | ggregory@apache.org  <ma...@apache.org>
> Java Persistence with Hibernate, Second Edition <http://www.manning.com/bauer3/>
> JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
> Spring Batch in Action <http://www.manning.com/templier/>
> Blog: http://garygregory.wordpress.com <http://garygregory.wordpress.com/> 
> Home: http://garygregory.com/ <http://garygregory.com/>
> Tweet! http://twitter.com/GaryGregory <http://twitter.com/GaryGregory>

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Gary Gregory <ga...@gmail.com>.
On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers <ra...@dslextreme.com>
wrote:

> Gary,
>
> RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the
> Routes class to execute the script in the call to getPattern and return the
> result if there is a script?
>

That's what Routes.getPattern() does (see this very commit thread):

@@ -90,12 +155,26 @@ public final class Routes {
      * @return the pattern.
      */
     public String getPattern() {
+        if (patternScript != null) {
+            final SimpleBindings bindings = new SimpleBindings();
+            bindings.put("configuration", configuration);
+            bindings.put("statusLogger", LOGGER);
+            final Object object =
configuration.getScriptManager().execute(patternScript.getName(),
bindings);
+            return Objects.toString(object, null);
+        }
         return pattern;
     }

Gary


>
> Ralph
>
> > On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
> >
> > Repository: logging-log4j2
> > Updated Branches:
> >  refs/heads/master 3846e2a87 -> e0f29d9ad
> >
> >
> > [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
> > in a Routes element.
> >
> > Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
> > Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/
> commit/e0f29d9a
> > Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/
> e0f29d9a
> > Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/
> e0f29d9a
> >
> > Branch: refs/heads/master
> > Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
> > Parents: 3846e2a
> > Author: Gary Gregory <gg...@apache.org>
> > Authored: Tue Sep 13 21:59:59 2016 -0700
> > Committer: Gary Gregory <gg...@apache.org>
> > Committed: Tue Sep 13 21:59:59 2016 -0700
> >
> > ----------------------------------------------------------------------
> > .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
> > .../core/appender/routing/RoutingAppender.java  |  19 ++-
> > .../routing/RoutesScriptAppenderTest.java       | 130
> +++++++++++++++++++
> > .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
> > .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
> > src/site/xdoc/manual/appenders.xml              |  27 +++-
> > 6 files changed, 340 insertions(+), 40 deletions(-)
> > ----------------------------------------------------------------------
> >
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
> core/appender/routing/Routes.java
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> b/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/Routes.java
> > index c95b64a..33fccd7 100644
> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/Routes.java
> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/Routes.java
> > @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.
> appender.routing;
> >
> > import java.util.Objects;
> >
> > +import javax.script.SimpleBindings;
> > +
> > import org.apache.logging.log4j.Logger;
> > +import org.apache.logging.log4j.core.config.Configuration;
> > 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.
> PluginBuilderFactory;
> > +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.validation.
> constraints.Required;
> > +import org.apache.logging.log4j.core.script.AbstractScript;
> > import org.apache.logging.log4j.status.StatusLogger;
> >
> > /**
> > @@ -33,54 +39,113 @@ public final class Routes {
> >
> >     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>
> {
> >
> > +        @PluginConfiguration
> > +        private Configuration configuration;
> > +
> >         @PluginAttribute("pattern")
> >         private String pattern;
> >
> > -        @PluginElement("Routes")
> > +        @PluginElement("Script")
> > +        private AbstractScript patternScript;
> > +
> > +        @PluginElement("Routes")
> > +        @Required
> >         private Route[] routes;
> >
> >         @Override
> >         public Routes build() {
> >             if (routes == null || routes.length == 0) {
> > -                LOGGER.error("No routes configured");
> > +                LOGGER.error("No Routes configured.");
> >                 return null;
> >             }
> > -            return new Routes(pattern, routes);
> > +            if (patternScript != null && pattern != null) {
> > +                LOGGER.warn("In a Routes element, you must configure
> either a Script element or a pattern attribute.");
> > +            }
> > +            if (patternScript != null) {
> > +                if (configuration == null) {
> > +                    LOGGER.error("No Configuration defined for Routes;
> required for Script");
> > +                } else {
> > +                    configuration.getScriptManager().addScript(
> patternScript);
> > +                }
> > +            }
> > +            return new Routes(configuration, patternScript, pattern,
> routes);
> > +        }
> > +
> > +        public Configuration getConfiguration() {
> > +            return configuration;
> >         }
> >
> >         public String getPattern() {
> >             return pattern;
> >         }
> >
> > +        public AbstractScript getPatternScript() {
> > +            return patternScript;
> > +        }
> > +
> >         public Route[] getRoutes() {
> >             return routes;
> >         }
> >
> > -        public Builder withPattern(@SuppressWarnings("hiding") String
> pattern) {
> > +        public Builder withConfiguration(@SuppressWarnings("hiding")
> final Configuration configuration) {
> > +            this.configuration = configuration;
> > +            return this;
> > +        }
> > +
> > +        public Builder withPattern(@SuppressWarnings("hiding") final
> String pattern) {
> >             this.pattern = pattern;
> >             return this;
> >         }
> >
> > -        public Builder withRoutes(@SuppressWarnings("hiding") Route[]
> routes) {
> > +        public Builder withPatternScript(@SuppressWarnings("hiding")
> final AbstractScript patternScript) {
> > +            this.patternScript = patternScript;
> > +            return this;
> > +        }
> > +
> > +        public Builder withRoutes(@SuppressWarnings("hiding") final
> Route[] routes) {
> >             this.routes = routes;
> >             return this;
> >         }
> >
> >     }
> >
> > +    private static final Logger LOGGER = StatusLogger.getLogger();
> > +
> > +    /**
> > +     * Creates the Routes.
> > +     * @param pattern The pattern.
> > +     * @param routes An array of Route elements.
> > +     * @return The Routes container.
> > +     * @deprecated since 2.7; use {@link #newBuilder()}.
> > +     */
> > +    @Deprecated
> > +    public static Routes createRoutes(
> > +            final String pattern,
> > +            final Route... routes) {
> > +        if (routes == null || routes.length == 0) {
> > +            LOGGER.error("No routes configured");
> > +            return null;
> > +        }
> > +        return new Routes(null, null, pattern, routes);
> > +    }
> > +
> >     @PluginBuilderFactory
> >     public static Builder newBuilder() {
> >         return new Builder();
> >     }
> > -
> > -    private static final Logger LOGGER = StatusLogger.getLogger();
> > -
> > +
> > +    private final Configuration configuration;
> > +
> >     private final String pattern;
> >
> > +    private final AbstractScript patternScript;
> > +
> >     // TODO Why not make this a Map or add a Map.
> >     private final Route[] routes;
> >
> > -    private Routes(final String pattern, final Route... routes) {
> > +    private Routes(final Configuration configuration, final
> AbstractScript patternScript, final String pattern, final Route... routes) {
> > +        this.configuration = configuration;
> > +        this.patternScript = patternScript;
> >         this.pattern = pattern;
> >         this.routes = routes;
> >     }
> > @@ -90,12 +155,26 @@ public final class Routes {
> >      * @return the pattern.
> >      */
> >     public String getPattern() {
> > +        if (patternScript != null) {
> > +            final SimpleBindings bindings = new SimpleBindings();
> > +            bindings.put("configuration", configuration);
> > +            bindings.put("statusLogger", LOGGER);
> > +            final Object object = configuration.
> getScriptManager().execute(patternScript.getName(), bindings);
> > +            return Objects.toString(object, null);
> > +        }
> >         return pattern;
> >     }
> >
> > -    public Route getRoute(String key) {
> > -        for (int i = 0; i < routes.length; i++) {
> > -            final Route route = routes[i];
> > +    /**
> > +     * Gets the optional script that decides which route to pick.
> > +     * @return the optional script that decides which route to pick.
> May be null.
> > +     */
> > +    public AbstractScript getPatternScript() {
> > +        return patternScript;
> > +    }
> > +
> > +    public Route getRoute(final String key) {
> > +        for (final Route route : routes) {
> >             if (Objects.equals(route.getKey(), key)) {
> >                 return route;
> >             }
> > @@ -127,22 +206,4 @@ public final class Routes {
> >
> >     }
> >
> > -    /**
> > -     * Creates the Routes.
> > -     * @param pattern The pattern.
> > -     * @param routes An array of Route elements.
> > -     * @return The Routes container.
> > -     * @deprecated since 2.7; use {@link #newBuilder()}.
> > -     */
> > -    @Deprecated
> > -    public static Routes createRoutes(
> > -            final String pattern,
> > -            final Route... routes) {
> > -        if (routes == null || routes.length == 0) {
> > -            LOGGER.error("No routes configured");
> > -            return null;
> > -        }
> > -        return new Routes(pattern, routes);
> > -    }
> > -
> > }
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/
> core/appender/routing/RoutingAppender.java
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/RoutingAppender.java b/log4j-core/src/main/java/
> org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> > index 4471333..78fddbc 100644
> > --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/RoutingAppender.java
> > +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/
> appender/routing/RoutingAppender.java
> > @@ -72,15 +72,16 @@ public final class RoutingAppender extends
> AbstractAppender {
> >
> >         @Override
> >         public RoutingAppender build() {
> > -            if (getName() == null) {
> > -                LOGGER.error("No name defined for RoutingAppender");
> > +            final String name = getName();
> > +            if (name == null) {
> > +                LOGGER.error("No name defined for this
> RoutingAppender");
> >                 return null;
> >             }
> >             if (routes == null) {
> > -                LOGGER.error("No routes defined for RoutingAppender");
> > +                LOGGER.error("No routes defined for RoutingAppender
> {}", name);
> >                 return null;
> >             }
> > -            return new RoutingAppender(getName(), getFilter(),
> isIgnoreExceptions(), routes, rewritePolicy,
> > +            return new RoutingAppender(name, getFilter(),
> isIgnoreExceptions(), routes, rewritePolicy,
> >                     configuration, purgePolicy, defaultRouteScript);
> >         }
> >
> > @@ -173,7 +174,7 @@ public final class RoutingAppender extends
> AbstractAppender {
> >     public void start() {
> >         if (defaultRouteScript != null) {
> >             if (configuration == null) {
> > -                error("No Configuration defined for RoutingAppender;
> required for DefaultRouteScript");
> > +                error("No Configuration defined for RoutingAppender;
> required for Script element.");
> >             } else {
> >                 configuration.getScriptManager().addScript(
> defaultRouteScript);
> >                 final SimpleBindings bindings = new SimpleBindings();
> > @@ -352,4 +353,12 @@ public final class RoutingAppender extends
> AbstractAppender {
> >     public RewritePolicy getRewritePolicy() {
> >         return rewritePolicy;
> >     }
> > +
> > +    public Routes getRoutes() {
> > +        return routes;
> > +    }
> > +
> > +    public Configuration getConfiguration() {
> > +        return configuration;
> > +    }
> > }
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/
> core/appender/routing/RoutesScriptAppenderTest.java
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/
> appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/
> org/apache/logging/log4j/core/appender/routing/
> RoutesScriptAppenderTest.java
> > new file mode 100644
> > index 0000000..7d90f6b
> > --- /dev/null
> > +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/
> appender/routing/RoutesScriptAppenderTest.java
> > @@ -0,0 +1,130 @@
> > +/*
> > + * 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.appender.routing;
> > +
> > +import static org.junit.Assert.assertNotNull;
> > +import static org.junit.Assert.assertTrue;
> > +
> > +import java.util.List;
> > +import java.util.Map;
> > +
> > +import org.apache.logging.log4j.core.LogEvent;
> > +import org.apache.logging.log4j.core.Logger;
> > +import org.apache.logging.log4j.core.config.AppenderControl;
> > +import org.apache.logging.log4j.junit.LoggerContextRule;
> > +import org.apache.logging.log4j.test.appender.ListAppender;
> > +import org.junit.Assert;
> > +import org.junit.Rule;
> > +import org.junit.Test;
> > +import org.junit.runner.RunWith;
> > +import org.junit.runners.Parameterized;
> > +
> > +/**
> > + *
> > + */
> > +@RunWith(Parameterized.class)
> > +public class RoutesScriptAppenderTest {
> > +
> > +    @Parameterized.Parameters(name = "{0}")
> > +    public static String[] getParameters() {
> > +        return new String[] {
> > +                "log4j-routing-routes-script-groovy.xml",
> > +                "log4j-routing-routes-script-javascript.xml" };
> > +    }
> > +
> > +    @Rule
> > +    public final LoggerContextRule loggerContextRule;
> > +
> > +    public RoutesScriptAppenderTest(final String configLocation) {
> > +        this.loggerContextRule = new LoggerContextRule(configLocation);
> > +    }
> > +
> > +    private ListAppender getListAppender() {
> > +        final String key = "Service2";
> > +        final RoutingAppender routingAppender = getRoutingAppender();
> > +        Assert.assertTrue(routingAppender.isStarted());
> > +        final Map<String, AppenderControl> appenders =
> routingAppender.getAppenders();
> > +        final AppenderControl appenderControl = appenders.get(key);
> > +        assertNotNull("No appender control generated for '" + key + "';
> appenders = " + appenders, appenderControl);
> > +        final ListAppender listAppender = (ListAppender)
> appenderControl.getAppender();
> > +        return listAppender;
> > +    }
> > +
> > +    private RoutingAppender getRoutingAppender() {
> > +        return loggerContextRule.getRequiredAppender("Routing",
> RoutingAppender.class);
> > +    }
> > +
> > +    private void logAndCheck() {
> > +        final Logger logger = loggerContextRule.getLogger(
> RoutesScriptAppenderTest.class);
> > +        logger.error("Hello");
> > +        final ListAppender listAppender = getListAppender();
> > +        final List<LogEvent> list = listAppender.getEvents();
> > +        assertNotNull("No events generated", list);
> > +        assertTrue("Incorrect number of events. Expected 1, got " +
> list.size(), list.size() == 1);
> > +        logger.error("World");
> > +        assertTrue("Incorrect number of events. Expected 2, got " +
> list.size(), list.size() == 2);
> > +    }
> > +
> > +    @Test(expected = AssertionError.class)
> > +    public void testAppenderAbsence() {
> > +        loggerContextRule.getListAppender("List1");
> > +    }
> > +
> > +    @Test
> > +    public void testListAppenderPresence() {
> > +        // No appender until an event is routed, even thought we
> initialized the default route on startup.
> > +        Assert.assertNull("No appender control generated",
> getRoutingAppender().getAppenders().get("Service2"));
> > +    }
> > +
> > +    @Test
> > +    public void testNoPurgePolicy() {
> > +        // No PurgePolicy in this test
> > +        Assert.assertNull("Unexpected PurgePolicy",
> getRoutingAppender().getPurgePolicy());
> > +    }
> > +
> > +    @Test
> > +    public void testNoRewritePolicy() {
> > +        // No RewritePolicy in this test
> > +        Assert.assertNull("Unexpected RewritePolicy",
> getRoutingAppender().getRewritePolicy());
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingAppenderRoutes() {
> > +        final RoutingAppender routingAppender = getRoutingAppender();
> > +        Assert.assertNull(routingAppender.getDefaultRouteScript());
> > +        Assert.assertNull(routingAppender.getDefaultRoute());
> > +        final Routes routes = routingAppender.getRoutes();
> > +        Assert.assertNotNull(routes);
> > +        Assert.assertNotNull(routes.getPatternScript());
> > +        Assert.assertEquals("Service2", routes.getPattern());
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingAppenderPresence() {
> > +        getRoutingAppender();
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingPresence1() {
> > +        logAndCheck();
> > +    }
> > +
> > +    @Test
> > +    public void testRoutingPresence2() {
> > +        logAndCheck();
> > +    }
> > +}
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/log4j-core/src/test/resources/log4j-routing-
> routes-script-groovy.xml
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
> b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
> > new file mode 100644
> > index 0000000..83121ea
> > --- /dev/null
> > +++ b/log4j-core/src/test/resources/log4j-routing-
> routes-script-groovy.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="WARN" name="RoutingTest">
> > +  <Appenders>
> > +    <Routing name="Routing">
> > +      <Routes>
> > +        <Script name="RoutingInit" language="groovy"><![CDATA[
> > +          if ("OSNameFoo".contains("Foo")) {
> > +            return "Service2";
> > +          }
> > +          return "Service1";]]>
> > +        </Script>
> > +        <Route key="Service1">
> > +          <List name="List1" />
> > +        </Route>
> > +        <Route key="Service2">
> > +          <List name="List2" />
> > +        </Route>
> > +      </Routes>
> > +    </Routing>
> > +  </Appenders>
> > +  <Loggers>
> > +    <Root level="error">
> > +      <AppenderRef ref="Routing" />
> > +    </Root>
> > +  </Loggers>
> > +</Configuration>
> > \ No newline at end of file
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/log4j-core/src/test/resources/log4j-routing-
> routes-script-javascript.xml
> > ----------------------------------------------------------------------
> > diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> > new file mode 100644
> > index 0000000..e672aea
> > --- /dev/null
> > +++ b/log4j-core/src/test/resources/log4j-routing-
> routes-script-javascript.xml
> > @@ -0,0 +1,40 @@
> > +<?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="WARN" name="RoutingTest">
> > +  <Appenders>
> > +    <Routing name="Routing">
> > +      <Routes>
> > +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
> > +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
> > +        </Script>
> > +        <Route key="Service1">
> > +          <List name="List1" />
> > +        </Route>
> > +        <Route key="Service2">
> > +          <List name="List2" />
> > +        </Route>
> > +      </Routes>
> > +    </Routing>
> > +  </Appenders>
> > +  <Loggers>
> > +    <Root level="error">
> > +      <AppenderRef ref="Routing" />
> > +    </Root>
> > +  </Loggers>
> > +</Configuration>
> > \ No newline at end of file
> >
> > http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/
> e0f29d9a/src/site/xdoc/manual/appenders.xml
> > ----------------------------------------------------------------------
> > diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/
> appenders.xml
> > index 2d3d361..a87d9b7 100644
> > --- a/src/site/xdoc/manual/appenders.xml
> > +++ b/src/site/xdoc/manual/appenders.xml
> > @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends
> AbstractLogEventWrapperEntity {
> >              Appender may be an appender previously configured and may
> be referenced by its name or the
> >              Appender can be dynamically created as needed. The
> RoutingAppender should be configured after any
> >              Appenders it references to allow it to shut down properly.
> > -          </p>
> > +           </p>
> > +           <p>
> > +             You can also configure a RoutingAppender with scripts: you
> can run a script when the appender starts
> > +             and when a route is chosen for an log event.
> > +           </p>
> >           <table>
> >             <caption align="top">RoutingAppender Parameters</caption>
> >             <tr>
> > @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends
> AbstractLogEventWrapperEntity {
> >               <th>Description</th>
> >             </tr>
> >             <tr>
> > -              <td>filter</td>
> > +              <td>Filter</td>
> >               <td>Filter</td>
> >               <td>A Filter to determine if the event should be handled
> by this Appender. More than one Filter
> >               may be used by using a CompositeFilter.</td>
> > @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends
> AbstractLogEventWrapperEntity {
> >               <td>The name of the Appender.</td>
> >             </tr>
> >             <tr>
> > -              <td>rewritePolicy</td>
> > +              <td>RewritePolicy</td>
> >               <td>RewritePolicy</td>
> >               <td>The RewritePolicy that will manipulate the
> LogEvent.</td>
> >             </tr>
> >             <tr>
> > -              <td>routes</td>
> > +              <td>Routes</td>
> >               <td>Routes</td>
> >               <td>Contains one or more Route declarations to identify
> the criteria for choosing Appenders.</td>
> >             </tr>
> >             <tr>
> > +              <td>Script</td>
> > +              <td>Script</td>
> > +              <td>This Script runs when Log4j starts the
> RoutingAppender and returns a String Route key to determine
> > +                the default Route.</td>
> > +            </tr>
> > +            <tr>
> >               <td>ignoreExceptions</td>
> >               <td>boolean</td>
> >               <td>The default is <code>true</code>, causing exceptions
> encountered while appending events to be
> > @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends
> AbstractLogEventWrapperEntity {
> >           </table>
> >           <h4>Routes</h4>
> >             <p>
> > -              The Routes element accepts a single, required attribute
> named "pattern". The pattern is evaluated
> > +              The Routes element accepts a single attribute named
> "pattern". The pattern is evaluated
> >               against all the registered Lookups and the result is used
> to select a Route. Each Route may be
> >               configured with a key. If the key matches the result of
> evaluating the pattern then that Route
> >               will be selected. If no key is specified on a Route then
> that Route is the default. Only one Route
> >               can be configured as the default.
> >             </p>
> >             <p>
> > +              The Routes element may contain a Script child element. If
> specified, the Script is run for each
> > +              log event and returns the String Route key to use.
> > +            </p>
> > +            <p>
> > +              You must specify either the pattern attribute or the
> Script element, but not both.
> > +            </p>
> > +            <p>
> >               Each Route must reference an Appender. If the Route
> contains a ref attribute then the
> >               Route will reference an Appender that was defined in the
> configuration. If the Route contains an
> >               Appender definition then an Appender will be created
> within the context of the RoutingAppender and
> >
> >
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
> For additional commands, e-mail: log4j-dev-help@logging.apache.org
>
>


-- 
E-Mail: garydgregory@gmail.com | ggregory@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Re: logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script in a Routes element.

Posted by Ralph Goers <ra...@dslextreme.com>.
Gary,

RoutingAppender calls routes.getPattern().  Wouldn’t it make sense for the Routes class to execute the script in the call to getPattern and return the result if there is a script?

Ralph

> On Sep 13, 2016, at 10:00 PM, ggregory@apache.org wrote:
> 
> Repository: logging-log4j2
> Updated Branches:
>  refs/heads/master 3846e2a87 -> e0f29d9ad
> 
> 
> [LOG4J2-1578] RoutingAppender can be configured with scripts. Add Script
> in a Routes element. 
> 
> Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
> Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/e0f29d9a
> Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/e0f29d9a
> Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/e0f29d9a
> 
> Branch: refs/heads/master
> Commit: e0f29d9ad73aa4579cdc2d71ae5e8c01dd4f9f9c
> Parents: 3846e2a
> Author: Gary Gregory <gg...@apache.org>
> Authored: Tue Sep 13 21:59:59 2016 -0700
> Committer: Gary Gregory <gg...@apache.org>
> Committed: Tue Sep 13 21:59:59 2016 -0700
> 
> ----------------------------------------------------------------------
> .../log4j/core/appender/routing/Routes.java     | 121 ++++++++++++-----
> .../core/appender/routing/RoutingAppender.java  |  19 ++-
> .../routing/RoutesScriptAppenderTest.java       | 130 +++++++++++++++++++
> .../log4j-routing-routes-script-groovy.xml      |  43 ++++++
> .../log4j-routing-routes-script-javascript.xml  |  40 ++++++
> src/site/xdoc/manual/appenders.xml              |  27 +++-
> 6 files changed, 340 insertions(+), 40 deletions(-)
> ----------------------------------------------------------------------
> 
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> index c95b64a..33fccd7 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
> @@ -18,11 +18,17 @@ package org.apache.logging.log4j.core.appender.routing;
> 
> import java.util.Objects;
> 
> +import javax.script.SimpleBindings;
> +
> import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.core.config.Configuration;
> 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.PluginBuilderFactory;
> +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.validation.constraints.Required;
> +import org.apache.logging.log4j.core.script.AbstractScript;
> import org.apache.logging.log4j.status.StatusLogger;
> 
> /**
> @@ -33,54 +39,113 @@ public final class Routes {
> 
>     public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes>  {
> 
> +        @PluginConfiguration 
> +        private Configuration configuration;
> +
>         @PluginAttribute("pattern") 
>         private String pattern;
> 
> -        @PluginElement("Routes") 
> +        @PluginElement("Script")
> +        private AbstractScript patternScript;
> +
> +        @PluginElement("Routes")
> +        @Required
>         private Route[] routes;
> 
>         @Override
>         public Routes build() {
>             if (routes == null || routes.length == 0) {
> -                LOGGER.error("No routes configured");
> +                LOGGER.error("No Routes configured.");
>                 return null;
>             }
> -            return new Routes(pattern, routes);
> +            if (patternScript != null && pattern != null) {
> +                LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute.");
> +            }
> +            if (patternScript != null) {
> +                if (configuration == null) {
> +                    LOGGER.error("No Configuration defined for Routes; required for Script");
> +                } else {
> +                    configuration.getScriptManager().addScript(patternScript);
> +                }
> +            }
> +            return new Routes(configuration, patternScript, pattern, routes);
> +        }
> +
> +        public Configuration getConfiguration() {
> +            return configuration;
>         }
> 
>         public String getPattern() {
>             return pattern;
>         }
> 
> +        public AbstractScript getPatternScript() {
> +            return patternScript;
> +        }
> +
>         public Route[] getRoutes() {
>             return routes;
>         }
> 
> -        public Builder withPattern(@SuppressWarnings("hiding") String pattern) {
> +        public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
> +            this.configuration = configuration;
> +            return this;
> +        }
> +
> +        public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
>             this.pattern = pattern;
>             return this;
>         }
> 
> -        public Builder withRoutes(@SuppressWarnings("hiding") Route[] routes) {
> +        public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
> +            this.patternScript = patternScript;
> +            return this;
> +        }
> +
> +        public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
>             this.routes = routes;
>             return this;
>         }
> 
>     }
> 
> +    private static final Logger LOGGER = StatusLogger.getLogger();
> +
> +    /**
> +     * Creates the Routes.
> +     * @param pattern The pattern.
> +     * @param routes An array of Route elements.
> +     * @return The Routes container.
> +     * @deprecated since 2.7; use {@link #newBuilder()}.
> +     */
> +    @Deprecated
> +    public static Routes createRoutes(
> +            final String pattern,
> +            final Route... routes) {
> +        if (routes == null || routes.length == 0) {
> +            LOGGER.error("No routes configured");
> +            return null;
> +        }
> +        return new Routes(null, null, pattern, routes);
> +    }
> +
>     @PluginBuilderFactory
>     public static Builder newBuilder() {
>         return new Builder();
>     }
> -
> -    private static final Logger LOGGER = StatusLogger.getLogger();
> -
> +    
> +    private final Configuration configuration;
> +    
>     private final String pattern;
> 
> +    private final AbstractScript patternScript;
> +
>     // TODO Why not make this a Map or add a Map.
>     private final Route[] routes;
> 
> -    private Routes(final String pattern, final Route... routes) {
> +    private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) {
> +        this.configuration = configuration;
> +        this.patternScript = patternScript;
>         this.pattern = pattern;
>         this.routes = routes;
>     }
> @@ -90,12 +155,26 @@ public final class Routes {
>      * @return the pattern.
>      */
>     public String getPattern() {
> +        if (patternScript != null) {
> +            final SimpleBindings bindings = new SimpleBindings();
> +            bindings.put("configuration", configuration);
> +            bindings.put("statusLogger", LOGGER);
> +            final Object object = configuration.getScriptManager().execute(patternScript.getName(), bindings);
> +            return Objects.toString(object, null);
> +        }
>         return pattern;
>     }
> 
> -    public Route getRoute(String key) {
> -        for (int i = 0; i < routes.length; i++) {
> -            final Route route = routes[i];
> +    /**
> +     * Gets the optional script that decides which route to pick.
> +     * @return the optional script that decides which route to pick. May be null.
> +     */
> +    public AbstractScript getPatternScript() {
> +        return patternScript;
> +    }
> +
> +    public Route getRoute(final String key) {
> +        for (final Route route : routes) {
>             if (Objects.equals(route.getKey(), key)) {
>                 return route;
>             }
> @@ -127,22 +206,4 @@ public final class Routes {
> 
>     }
> 
> -    /**
> -     * Creates the Routes.
> -     * @param pattern The pattern.
> -     * @param routes An array of Route elements.
> -     * @return The Routes container.
> -     * @deprecated since 2.7; use {@link #newBuilder()}.
> -     */
> -    @Deprecated
> -    public static Routes createRoutes(
> -            final String pattern,
> -            final Route... routes) {
> -        if (routes == null || routes.length == 0) {
> -            LOGGER.error("No routes configured");
> -            return null;
> -        }
> -        return new Routes(pattern, routes);
> -    }
> -
> }
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> index 4471333..78fddbc 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
> @@ -72,15 +72,16 @@ public final class RoutingAppender extends AbstractAppender {
> 
>         @Override
>         public RoutingAppender build() {
> -            if (getName() == null) {
> -                LOGGER.error("No name defined for RoutingAppender");
> +            final String name = getName();
> +            if (name == null) {
> +                LOGGER.error("No name defined for this RoutingAppender");
>                 return null;
>             }
>             if (routes == null) {
> -                LOGGER.error("No routes defined for RoutingAppender");
> +                LOGGER.error("No routes defined for RoutingAppender {}", name);
>                 return null;
>             }
> -            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
> +            return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
>                     configuration, purgePolicy, defaultRouteScript);
>         }
> 
> @@ -173,7 +174,7 @@ public final class RoutingAppender extends AbstractAppender {
>     public void start() {
>         if (defaultRouteScript != null) {
>             if (configuration == null) {
> -                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
> +                error("No Configuration defined for RoutingAppender; required for Script element.");
>             } else {
>                 configuration.getScriptManager().addScript(defaultRouteScript);
>                 final SimpleBindings bindings = new SimpleBindings();
> @@ -352,4 +353,12 @@ public final class RoutingAppender extends AbstractAppender {
>     public RewritePolicy getRewritePolicy() {
>         return rewritePolicy;
>     }
> +
> +    public Routes getRoutes() {
> +        return routes;
> +    }
> +
> +    public Configuration getConfiguration() {
> +        return configuration;
> +    }
> }
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
> new file mode 100644
> index 0000000..7d90f6b
> --- /dev/null
> +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
> @@ -0,0 +1,130 @@
> +/*
> + * 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.appender.routing;
> +
> +import static org.junit.Assert.assertNotNull;
> +import static org.junit.Assert.assertTrue;
> +
> +import java.util.List;
> +import java.util.Map;
> +
> +import org.apache.logging.log4j.core.LogEvent;
> +import org.apache.logging.log4j.core.Logger;
> +import org.apache.logging.log4j.core.config.AppenderControl;
> +import org.apache.logging.log4j.junit.LoggerContextRule;
> +import org.apache.logging.log4j.test.appender.ListAppender;
> +import org.junit.Assert;
> +import org.junit.Rule;
> +import org.junit.Test;
> +import org.junit.runner.RunWith;
> +import org.junit.runners.Parameterized;
> +
> +/**
> + *
> + */
> +@RunWith(Parameterized.class)
> +public class RoutesScriptAppenderTest {
> +
> +    @Parameterized.Parameters(name = "{0}")
> +    public static String[] getParameters() {
> +        return new String[] { 
> +                "log4j-routing-routes-script-groovy.xml",
> +                "log4j-routing-routes-script-javascript.xml" };
> +    }
> +
> +    @Rule
> +    public final LoggerContextRule loggerContextRule;
> +
> +    public RoutesScriptAppenderTest(final String configLocation) {
> +        this.loggerContextRule = new LoggerContextRule(configLocation);
> +    }
> +
> +    private ListAppender getListAppender() {
> +        final String key = "Service2";
> +        final RoutingAppender routingAppender = getRoutingAppender();
> +        Assert.assertTrue(routingAppender.isStarted());
> +        final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
> +        final AppenderControl appenderControl = appenders.get(key);
> +        assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
> +        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
> +        return listAppender;
> +    }
> +
> +    private RoutingAppender getRoutingAppender() {
> +        return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class);
> +    }
> +
> +    private void logAndCheck() {
> +        final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
> +        logger.error("Hello");
> +        final ListAppender listAppender = getListAppender();
> +        final List<LogEvent> list = listAppender.getEvents();
> +        assertNotNull("No events generated", list);
> +        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
> +        logger.error("World");
> +        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
> +    }
> +
> +    @Test(expected = AssertionError.class)
> +    public void testAppenderAbsence() {
> +        loggerContextRule.getListAppender("List1");
> +    }
> +
> +    @Test
> +    public void testListAppenderPresence() {
> +        // No appender until an event is routed, even thought we initialized the default route on startup.
> +        Assert.assertNull("No appender control generated", getRoutingAppender().getAppenders().get("Service2"));
> +    }
> +
> +    @Test
> +    public void testNoPurgePolicy() {
> +        // No PurgePolicy in this test
> +        Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy());
> +    }
> +
> +    @Test
> +    public void testNoRewritePolicy() {
> +        // No RewritePolicy in this test
> +        Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy());
> +    }
> +
> +    @Test
> +    public void testRoutingAppenderRoutes() {
> +        final RoutingAppender routingAppender = getRoutingAppender();
> +        Assert.assertNull(routingAppender.getDefaultRouteScript());
> +        Assert.assertNull(routingAppender.getDefaultRoute());
> +        final Routes routes = routingAppender.getRoutes();
> +        Assert.assertNotNull(routes);
> +        Assert.assertNotNull(routes.getPatternScript());
> +        Assert.assertEquals("Service2", routes.getPattern());
> +    }
> +
> +    @Test
> +    public void testRoutingAppenderPresence() {
> +        getRoutingAppender();
> +    }
> +
> +    @Test
> +    public void testRoutingPresence1() {
> +        logAndCheck();
> +    }
> +
> +    @Test
> +    public void testRoutingPresence2() {
> +        logAndCheck();
> +    }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.xml
> new file mode 100644
> index 0000000..83121ea
> --- /dev/null
> +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-groovy.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="WARN" name="RoutingTest">
> +  <Appenders>
> +    <Routing name="Routing">
> +      <Routes>
> +        <Script name="RoutingInit" language="groovy"><![CDATA[
> +          if ("OSNameFoo".contains("Foo")) {
> +            return "Service2";
> +          }
> +          return "Service1";]]>
> +        </Script>
> +        <Route key="Service1">
> +          <List name="List1" />
> +        </Route>
> +        <Route key="Service2">
> +          <List name="List2" />
> +        </Route>
> +      </Routes>
> +    </Routing>
> +  </Appenders>
> +  <Loggers>
> +    <Root level="error">
> +      <AppenderRef ref="Routing" />
> +    </Root>
> +  </Loggers>
> +</Configuration>
> \ No newline at end of file
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> ----------------------------------------------------------------------
> diff --git a/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> new file mode 100644
> index 0000000..e672aea
> --- /dev/null
> +++ b/log4j-core/src/test/resources/log4j-routing-routes-script-javascript.xml
> @@ -0,0 +1,40 @@
> +<?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="WARN" name="RoutingTest">
> +  <Appenders>
> +    <Routing name="Routing">
> +      <Routes>
> +        <Script name="RoutingInit" language="JavaScript"><![CDATA[
> +          "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
> +        </Script>
> +        <Route key="Service1">
> +          <List name="List1" />
> +        </Route>
> +        <Route key="Service2">
> +          <List name="List2" />
> +        </Route>
> +      </Routes>
> +    </Routing>
> +  </Appenders>
> +  <Loggers>
> +    <Root level="error">
> +      <AppenderRef ref="Routing" />
> +    </Root>
> +  </Loggers>
> +</Configuration>
> \ No newline at end of file
> 
> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e0f29d9a/src/site/xdoc/manual/appenders.xml
> ----------------------------------------------------------------------
> diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
> index 2d3d361..a87d9b7 100644
> --- a/src/site/xdoc/manual/appenders.xml
> +++ b/src/site/xdoc/manual/appenders.xml
> @@ -3279,7 +3279,11 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>              Appender may be an appender previously configured and may be referenced by its name or the
>              Appender can be dynamically created as needed. The RoutingAppender should be configured after any
>              Appenders it references to allow it to shut down properly.
> -          </p>
> +           </p>
> +           <p>
> +             You can also configure a RoutingAppender with scripts: you can run a script when the appender starts 
> +             and when a route is chosen for an log event. 
> +           </p>
>           <table>
>             <caption align="top">RoutingAppender Parameters</caption>
>             <tr>
> @@ -3288,7 +3292,7 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>               <th>Description</th>
>             </tr>
>             <tr>
> -              <td>filter</td>
> +              <td>Filter</td>
>               <td>Filter</td>
>               <td>A Filter to determine if the event should be handled by this Appender. More than one Filter
>               may be used by using a CompositeFilter.</td>
> @@ -3299,16 +3303,22 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>               <td>The name of the Appender.</td>
>             </tr>
>             <tr>
> -              <td>rewritePolicy</td>
> +              <td>RewritePolicy</td>
>               <td>RewritePolicy</td>
>               <td>The RewritePolicy that will manipulate the LogEvent.</td>
>             </tr>
>             <tr>
> -              <td>routes</td>
> +              <td>Routes</td>
>               <td>Routes</td>
>               <td>Contains one or more Route declarations to identify the criteria for choosing Appenders.</td>
>             </tr>
>             <tr>
> +              <td>Script</td>
> +              <td>Script</td>
> +              <td>This Script runs when Log4j starts the RoutingAppender and returns a String Route key to determine 
> +                the default Route.</td>
> +            </tr>
> +            <tr>
>               <td>ignoreExceptions</td>
>               <td>boolean</td>
>               <td>The default is <code>true</code>, causing exceptions encountered while appending events to be
> @@ -3319,13 +3329,20 @@ public class JpaLogEntity extends AbstractLogEventWrapperEntity {
>           </table>
>           <h4>Routes</h4>
>             <p>
> -              The Routes element accepts a single, required attribute named "pattern". The pattern is evaluated
> +              The Routes element accepts a single attribute named "pattern". The pattern is evaluated
>               against all the registered Lookups and the result is used to select a Route. Each Route may be
>               configured with a key. If the key matches the result of evaluating the pattern then that Route
>               will be selected. If no key is specified on a Route then that Route is the default. Only one Route
>               can be configured as the default.
>             </p>
>             <p>
> +              The Routes element may contain a Script child element. If specified, the Script is run for each
> +              log event and returns the String Route key to use.
> +            </p>
> +            <p>
> +              You must specify either the pattern attribute or the Script element, but not both.
> +            </p>
> +            <p>
>               Each Route must reference an Appender. If the Route contains a ref attribute then the
>               Route will reference an Appender that was defined in the configuration. If the Route contains an
>               Appender definition then an Appender will be created within the context of the RoutingAppender and
> 
> 



---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org