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 <[email protected]> wrote: > > FYI: I just pushed site docs updates. > > Gary > > On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <[email protected] > <mailto:[email protected]>> wrote: > And I am not worried about concurrency around the log event. > > Gary > > On Wed, Sep 14, 2016 at 6:59 PM, Gary Gregory <[email protected] > <mailto:[email protected]>> 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 <[email protected] > <mailto:[email protected]>> wrote: > And obviously the Bindings variable should not be a class member. > > Ralph > >> On Sep 14, 2016, at 4:33 PM, Ralph Goers <[email protected] >> <mailto:[email protected]>> wrote: >> >> Oops. Never mind. You have if (bindings == null) … Don’t do that. >> >> Ralph >> >>> On Sep 14, 2016, at 4:29 PM, Gary Gregory <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> That's not how I have now. I'll revisit... >>> >>> >>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <[email protected] >>> <mailto:[email protected]>> 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 <[email protected] >>>> <mailto:[email protected]>> 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 <[email protected] >>>> <mailto:[email protected]>> 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 <[email protected] >>>>> <mailto:[email protected]>> 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 <[email protected] >>>>>> <mailto:[email protected]>> 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 <[email protected] >>>>>> <mailto:[email protected]>> 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 <[email protected] >>>>>>> <mailto:[email protected]>> wrote: >>>>>>> >>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers >>>>>>> <[email protected] <mailto:[email protected]>> 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, [email protected] >>>>>>> > <mailto:[email protected]> 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 <[email protected] >>>>>>> > <mailto:[email protected]>> >>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700 >>>>>>> > Committer: Gary Gregory <[email protected] >>>>>>> > <mailto:[email protected]>> >>>>>>> > 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: [email protected] >>>>>>> <mailto:[email protected]> >>>>>>> For additional commands, e-mail: [email protected] >>>>>>> <mailto:[email protected]> >>>>>>> >>>>>>> >>>>>>> >>>>>>> >>>>>>> -- >>>>>>> E-Mail: [email protected] <mailto:[email protected]> | >>>>>>> [email protected] <mailto:[email protected]> >>>>>>> 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: [email protected] <mailto:[email protected]> | >>>>>> [email protected] <mailto:[email protected]> >>>>>> 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: [email protected] <mailto:[email protected]> | >>>>> [email protected] <mailto:[email protected]> >>>>> 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: [email protected] <mailto:[email protected]> | > [email protected] <mailto:[email protected]> > 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: [email protected] <mailto:[email protected]> | > [email protected] <mailto:[email protected]> > 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: [email protected] <mailto:[email protected]> | > [email protected] <mailto:[email protected]> > 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>
