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, ggreg...@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 <ggreg...@apache.org> > Authored: Tue Sep 13 21:59:59 2016 -0700 > Committer: Gary Gregory <ggreg...@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-unsubscr...@logging.apache.org For additional commands, e-mail: log4j-dev-h...@logging.apache.org