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/13 09:30:37 UTC

logging-log4j2 git commit: [LOG4J2-1578] RoutingAppender can be configured with scripts. First implementation of the simplest case: initialize the default route from a script run at appender start time.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 82cd54656 -> 363bc187d


[LOG4J2-1578] RoutingAppender can be configured with scripts. First
implementation of the simplest case: initialize the default route from a
script run at appender start time.

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

Branch: refs/heads/master
Commit: 363bc187d96dbd67ae0e2db39637e46d3f1d9d1d
Parents: 82cd546
Author: Gary Gregory <gg...@apache.org>
Authored: Tue Sep 13 02:30:32 2016 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Tue Sep 13 02:30:32 2016 -0700

----------------------------------------------------------------------
 .../log4j/core/appender/routing/Routes.java     |  23 +++-
 .../core/appender/routing/RoutingAppender.java  |  90 ++++++++++---
 .../routing/DefaultRouteScriptAppenderTest.java | 130 +++++++++++++++++++
 ...og4j-routing-default-route-script-groovy.xml |  43 ++++++
 ...-routing-default-route-script-javascript.xml |  40 ++++++
 5 files changed, 303 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/363bc187/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 63a178c..aae4087 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
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.core.appender.routing;
 
+import java.util.Objects;
+
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
@@ -24,7 +26,7 @@ import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 
 /**
- * Used to contain the individual Route elements.
+ * Contains the individual Route elements.
  */
 @Plugin(name = "Routes", category = "Core", printObject = true)
 public final class Routes {
@@ -32,6 +34,8 @@ public final class Routes {
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private final String pattern;
+    
+    // TODO Why not make this a Map or add a Map.
     private final Route[] routes;
 
     private Routes(final String pattern, final Route... routes) {
@@ -47,6 +51,16 @@ public final class Routes {
         return pattern;
     }
 
+    public Route getRoute(String key) {
+        for (int i = 0; i < routes.length; i++) {
+            final Route route = routes[i];
+            if (Objects.equals(route.getKey(), key)) {
+                return route;
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns the array of Route elements.
      * @return an array of Route elements.
@@ -72,7 +86,7 @@ public final class Routes {
     }
 
     /**
-     * Create the Routes.
+     * Creates the Routes.
      * @param pattern The pattern.
      * @param routes An array of Route elements.
      * @return The Routes container.
@@ -81,14 +95,11 @@ public final class Routes {
     public static Routes createRoutes(
             @PluginAttribute("pattern") final String pattern,
             @PluginElement("Routes") final Route... routes) {
-        if (pattern == null) {
-            LOGGER.error("A pattern is required");
-            return null;
-        }
         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/363bc187/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 86427ca..4471333 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
@@ -18,10 +18,13 @@ package org.apache.logging.log4j.core.appender.routing;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 
+import javax.script.SimpleBindings;
+
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.LogEvent;
@@ -34,6 +37,7 @@ import org.apache.logging.log4j.core.config.plugins.Plugin;
 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.script.AbstractScript;
 import org.apache.logging.log4j.core.util.Booleans;
 
 /**
@@ -50,6 +54,10 @@ public final class RoutingAppender extends AbstractAppender {
     public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
             implements org.apache.logging.log4j.core.util.Builder<RoutingAppender> {
                 
+        // Does not work unless the element is called "Script", I wanted "DefaultRounteScript"...
+        @PluginElement("Script")
+        private AbstractScript defaultRouteScript;
+        
         @PluginElement("Routes") 
         private Routes routes;
         
@@ -65,14 +73,15 @@ public final class RoutingAppender extends AbstractAppender {
         @Override
         public RoutingAppender build() {
             if (getName() == null) {
-                LOGGER.error("No name provided for RoutingAppender");
+                LOGGER.error("No name defined for RoutingAppender");
                 return null;
             }
             if (routes == null) {
                 LOGGER.error("No routes defined for RoutingAppender");
                 return null;
             }
-            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy, getConfiguration(), purgePolicy);
+            return new RoutingAppender(getName(), getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
+                    configuration, purgePolicy, defaultRouteScript);
         }
 
         public Routes getRoutes() {
@@ -83,6 +92,10 @@ public final class RoutingAppender extends AbstractAppender {
             return configuration;
         }
 
+        public AbstractScript getDefaultRouteScript() {
+            return defaultRouteScript;
+        }
+
         public RewritePolicy getRewritePolicy() {
             return rewritePolicy;
         }
@@ -91,16 +104,24 @@ public final class RoutingAppender extends AbstractAppender {
             return purgePolicy;
         }
 
-        public void withRoutes(@SuppressWarnings("hiding") final Routes routes) {
+        public B withRoutes(@SuppressWarnings("hiding") final Routes routes) {
             this.routes = routes;
+            return asBuilder();
         }
 
-        public void withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
+        public B withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
             this.configuration = configuration;
+            return asBuilder();
         }
 
-        public void withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
+        public B withDefaultRouteScript(@SuppressWarnings("hiding") AbstractScript defaultRouteScript) {
+            this.defaultRouteScript = defaultRouteScript;
+            return asBuilder();
+        }
+        
+        public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
             this.rewritePolicy = rewritePolicy;
+            return asBuilder();
         }
 
         public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) {
@@ -116,22 +137,25 @@ public final class RoutingAppender extends AbstractAppender {
 
     private static final String DEFAULT_KEY = "ROUTING_APPENDER_DEFAULT";
     private final Routes routes;
-    private final Route defaultRoute;
-    private final Configuration config;
+    private Route defaultRoute;
+    private final Configuration configuration;
     private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>();
     private final RewritePolicy rewritePolicy;
     private final PurgePolicy purgePolicy;
+    private final AbstractScript defaultRouteScript;
 
     private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
-                            final RewritePolicy rewritePolicy, final Configuration config, final PurgePolicy purgePolicy) {
+            final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy,
+            AbstractScript defaultRouteScript) {
         super(name, filter, null, ignoreExceptions);
         this.routes = routes;
-        this.config = config;
+        this.configuration = configuration;
         this.rewritePolicy = rewritePolicy;
         this.purgePolicy = purgePolicy;
         if (this.purgePolicy != null) {
             this.purgePolicy.initialize(this);
         }
+        this.defaultRouteScript = defaultRouteScript;
         Route defRoute = null;
         for (final Route route : routes.getRoutes()) {
             if (route.getKey() == null) {
@@ -147,15 +171,30 @@ public final class RoutingAppender extends AbstractAppender {
 
     @Override
     public void start() {
+        if (defaultRouteScript != null) {
+            if (configuration == null) {
+                error("No Configuration defined for RoutingAppender; required for DefaultRouteScript");
+            } else {
+                configuration.getScriptManager().addScript(defaultRouteScript);
+                final SimpleBindings bindings = new SimpleBindings();
+                bindings.put("configuration", configuration);
+                bindings.put("statusLogger", LOGGER);
+                final Object object = configuration.getScriptManager().execute(defaultRouteScript.getName(), bindings);
+                final Route route = routes.getRoute(Objects.toString(object, null));
+                if (route != null) {
+                    defaultRoute = route;
+                }
+            }
+        }
         // Register all the static routes.
         for (final Route route : routes.getRoutes()) {
             if (route.getAppenderRef() != null) {
-                final Appender appender = config.getAppender(route.getAppenderRef());
+                final Appender appender = configuration.getAppender(route.getAppenderRef());
                 if (appender != null) {
                     final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey();
                     appenders.put(key, new AppenderControl(appender, null, null));
                 } else {
-                    LOGGER.error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
+                    error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored");
                 }
             }
         }
@@ -166,7 +205,7 @@ public final class RoutingAppender extends AbstractAppender {
     public boolean stop(final long timeout, final TimeUnit timeUnit) {
         setStopping();
         super.stop(timeout, timeUnit, false);
-        final Map<String, Appender> map = config.getAppenders();
+        final Map<String, Appender> map = configuration.getAppenders();
         for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
             final String name = entry.getValue().getAppender().getName();
             if (!map.containsKey(name)) {
@@ -182,7 +221,8 @@ public final class RoutingAppender extends AbstractAppender {
         if (rewritePolicy != null) {
             event = rewritePolicy.rewrite(event);
         }
-        final String key = config.getStrSubstitutor().replace(event, routes.getPattern());
+        final String pattern = routes.getPattern();
+        final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : defaultRoute.getKey();
         final AppenderControl control = getControl(key, event);
         if (control != null) {
             control.callAppender(event);
@@ -229,17 +269,17 @@ public final class RoutingAppender extends AbstractAppender {
         for (final Node node : routeNode.getChildren()) {
             if (node.getType().getElementName().equals("appender")) {
                 final Node appNode = new Node(node);
-                config.createConfiguration(appNode, event);
+                configuration.createConfiguration(appNode, event);
                 if (appNode.getObject() instanceof Appender) {
                     final Appender app = appNode.getObject();
                     app.start();
                     return app;
                 }
-                LOGGER.error("Unable to create Appender of type " + node.getName());
+                error("Unable to create Appender of type " + node.getName());
                 return null;
             }
         }
-        LOGGER.error("No Appender was configured for route " + route.getKey());
+        error("No Appender was configured for route " + route.getKey());
         return null;
     }
 
@@ -294,6 +334,22 @@ public final class RoutingAppender extends AbstractAppender {
             LOGGER.error("No routes defined for RoutingAppender");
             return null;
         }
-        return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy);
+        return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null);
+    }
+
+    public Route getDefaultRoute() {
+        return defaultRoute;
+    }
+
+    public AbstractScript getDefaultRouteScript() {
+        return defaultRouteScript;
+    }
+
+    public PurgePolicy getPurgePolicy() {
+        return purgePolicy;
+    }
+
+    public RewritePolicy getRewritePolicy() {
+        return rewritePolicy;
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/363bc187/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
new file mode 100644
index 0000000..44fb6de
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.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.EventLogger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.AppenderControl;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ *
+ */
+@RunWith(Parameterized.class)
+public class DefaultRouteScriptAppenderTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static String[] getParameters() {
+        return new String[] { 
+                // @Ignore "log4j-routing-default-route-script-groovy.xml",
+                "log4j-routing-default-route-script-javascript.xml" };
+    }
+
+    @Rule
+    public final LoggerContextRule loggerContextRule;
+
+    public DefaultRouteScriptAppenderTest(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 StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Alert");
+        EventLogger.logEvent(msg);
+        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);
+        EventLogger.logEvent(msg);
+        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 testRoutingAppenderDefaultRouteKey() {
+        final RoutingAppender routingAppender = getRoutingAppender();
+        Assert.assertNotNull(routingAppender.getDefaultRouteScript());
+        Assert.assertNotNull(routingAppender.getDefaultRoute());
+        Assert.assertEquals("Service2", routingAppender.getDefaultRoute().getKey());
+    }
+
+    @Test
+    public void testRoutingAppenderPresence() {
+        getRoutingAppender();
+    }
+
+    @Test
+    public void testRoutingPresence1() {
+        logAndCheck();
+    }
+
+    @Test
+    //@Ignore
+    public void testRoutingPresence2() {
+        logAndCheck();
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/363bc187/log4j-core/src/test/resources/log4j-routing-default-route-script-groovy.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-routing-default-route-script-groovy.xml b/log4j-core/src/test/resources/log4j-routing-default-route-script-groovy.xml
new file mode 100644
index 0000000..3d371a9
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-routing-default-route-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">
+      <Script name="RoutingInit" language="groovy"><![CDATA[
+         if ("OSNameFoo".contains("Foo")) {
+            return "Service2";
+         }
+         return "Service1";]]>
+      </Script>
+      <Routes>
+        <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/363bc187/log4j-core/src/test/resources/log4j-routing-default-route-script-javascript.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-routing-default-route-script-javascript.xml b/log4j-core/src/test/resources/log4j-routing-default-route-script-javascript.xml
new file mode 100644
index 0000000..abe99ea
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-routing-default-route-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">
+      <Script name="RoutingInit" language="JavaScript"><![CDATA[
+         "OSNameFoo".search("Foo") > -1 ? "Service2" : "Service1";]]>
+      </Script>
+      <Routes>
+        <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