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 <[email protected]> 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 <[email protected]> wrote: > > FYI: I just pushed site docs updates. > > Gary > > On Wed, Sep 14, 2016 at 7:00 PM, Gary Gregory <[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]> >> 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] >>> > 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]> >>>> 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]> >>>> wrote: >>>> >>>> That's not how I have now. I'll revisit... >>>> >>>> On Sep 14, 2016 4:27 PM, "Ralph Goers" <[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]> >>>>> 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]> 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]> 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]> >>>>>> 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]> 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]> >>>>>>> wrote: >>>>>>> >>>>>>> On Tue, Sep 13, 2016 at 10:33 PM, Ralph Goers < >>>>>>> [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] 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 <[email protected]> >>>>>>>> > Authored: Tue Sep 13 21:59:59 2016 -0700 >>>>>>>> > Committer: Gary Gregory <[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 >>>>>>>> > ------------------------------------------------------------ >>>>>>>> ---------- >>>>>>>> > 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: [email protected] >>>>>>>> For additional commands, e-mail: [email protected] >>>>>>>> >>>>>>>> >>>>>>> >>>>>>> >>>>>>> -- >>>>>>> E-Mail: [email protected] | [email protected] >>>>>>> <[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 >>>>>>> Home: http://garygregory.com/ >>>>>>> Tweet! http://twitter.com/GaryGregory >>>>>>> >>>>>>> >>>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> E-Mail: [email protected] | [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 >>>>>> Home: http://garygregory.com/ >>>>>> Tweet! http://twitter.com/GaryGregory >>>>>> >>>>>> >>>>>> >>>>> >>>>> >>>>> -- >>>>> E-Mail: [email protected] | [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 >>>>> Home: http://garygregory.com/ >>>>> Tweet! http://twitter.com/GaryGregory >>>>> >>>>> >>>>> >>>> >>>> >>> >>> >>> -- >>> E-Mail: [email protected] | [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 >>> Home: http://garygregory.com/ >>> Tweet! http://twitter.com/GaryGregory >>> >> >> >> >> -- >> E-Mail: [email protected] | [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 >> Home: http://garygregory.com/ >> Tweet! http://twitter.com/GaryGregory >> > > > > -- > E-Mail: [email protected] | [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 > Home: http://garygregory.com/ > Tweet! http://twitter.com/GaryGregory > > > -- E-Mail: [email protected] | [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 Home: http://garygregory.com/ Tweet! http://twitter.com/GaryGregory
