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
