Repository: logging-log4j2 Updated Branches: refs/heads/master d59bb7137 -> 21ac645e0
[LOG4J2-1597] Add a ScriptSelector Appender to create an Appender specified by a Script. First cut. See failing org.apache.logging.log4j.core.appender.ScriptSelectorAppenderTest. See status logger output. Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/79d9e113 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/79d9e113 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/79d9e113 Branch: refs/heads/master Commit: 79d9e11373b8246c7725cbf5c115be4a4b7c05f0 Parents: 5599aed Author: Gary Gregory <ggreg...@apache.org> Authored: Sun Sep 18 21:20:52 2016 -0700 Committer: Gary Gregory <ggreg...@apache.org> Committed: Sun Sep 18 21:20:52 2016 -0700 ---------------------------------------------------------------------- .../log4j/core/appender/AppenderSet.java | 135 ++++++++++++++++++ .../log4j/core/appender/ScriptSelector.java | 139 +++++++++++++++++++ .../appender/ScriptSelectorAppenderTest.java | 95 +++++++++++++ .../log4j-appender-selector-javascript.xml | 35 +++++ 4 files changed, 404 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/79d9e113/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java new file mode 100644 index 0000000..4403ab4 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java @@ -0,0 +1,135 @@ +/* + * 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; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginNode; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * A deferred plugin for appenders. + */ +@Plugin(name = "AppenderSet", category = "Core", printObject = true, deferChildren = true) +public class AppenderSet { + + public static class Builder implements org.apache.logging.log4j.core.util.Builder<AppenderSet> { + + @PluginNode + private Node node; + + @PluginConfiguration + @Required + private Configuration configuration; + + @Override + public AppenderSet build() { + if (configuration == null) { + LOGGER.error("Configuration is missing from AppenderNodeSet"); + } + final List<Node> children = node.getChildren(); + final Map<String, Node> map = new HashMap<>(node == null ? 0 : children.size()); + if (children == null) { + LOGGER.error("No child node in the AppenderSet {}", this); + return null; + } + for (final Node childNode : children) { + final String key = childNode.getAttributes().get("name"); + if (key == null) { + LOGGER.error("The attribute 'name' is missing from from the node {} in the AppenderNodeSet {}", + childNode, children); + } else { + map.put(key, childNode); + } + } + return new AppenderSet(configuration, map); + } + + public Node getNode() { + return node; + } + + public Configuration getConfiguration() { + return configuration; + } + + public Builder withNode(@SuppressWarnings("hiding") final Node node) { + this.node = node; + return this; + } + + public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + this.configuration = configuration; + return this; + } + + @Override + public String toString() { + return getClass().getName() + " [node=" + node + ", configuration=" + configuration + "]"; + } + + } + + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + + private final Configuration configuration; + private final Map<String, Node> nodeMap; + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + private AppenderSet(final Configuration configuration, final Map<String, Node> appenders) { + this.configuration = configuration; + this.nodeMap = appenders; + } + + public Appender createAppender(final String appenderName) { + final Node node = nodeMap.get(appenderName); + if (node == null) { + LOGGER.error("No node named {} in {}", appenderName, this); + return null; + } + if (node.getType().getElementName().equals("appender")) { + final Node appNode = new Node(node); + configuration.createConfiguration(appNode, null); + if (appNode.getObject() instanceof Appender) { + final Appender app = appNode.getObject(); + app.start(); + return app; + } + LOGGER.error("Unable to create Appender of type " + node.getName()); + return null; + } + LOGGER.error("No Appender was configured for name {} " + appenderName); + return null; + } + + public Map<String, Node> getAppenders() { + return nodeMap; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/79d9e113/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptSelector.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptSelector.java new file mode 100644 index 0000000..028f5b7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptSelector.java @@ -0,0 +1,139 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.Objects; + +import javax.script.Bindings; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +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.PluginBuilderAttribute; +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.core.script.ScriptManager; + +@Plugin(name = "ScriptSelector", category = "Core", elementType = "appender", printObject = true) +public class ScriptSelector extends AbstractAppender { + + /** + * Builds an appender. + */ + public static final class Builder implements org.apache.logging.log4j.core.util.Builder<Appender> { + + @PluginElement("AppenderSet") + @Required + private AppenderSet appenderSet; + + @PluginConfiguration + @Required + private Configuration configuration; + + @PluginBuilderAttribute + @Required + private String name; + + @PluginElement("Script") + @Required + private AbstractScript script; + + @Override + public Appender build() { + if (name == null) { + LOGGER.error("Name missing."); + } + if (script == null) { + LOGGER.error("Script missing for ScriptSelector appender {}", name); + } + if (appenderSet == null) { + LOGGER.error("AppenderSet missing for ScriptSelector appender {}", name); + } + if (configuration == null) { + LOGGER.error("Configuration missing for ScriptSelector appender {}", name); + } + final ScriptManager scriptManager = configuration.getScriptManager(); + scriptManager.addScript(script); + final Bindings bindings = scriptManager.createBindings(script); + final Object object = scriptManager.execute(script.getName(), bindings); + final String appenderName = Objects.toString(object, null); + final Appender appender = appenderSet.createAppender(appenderName); + // This feels like a hack and it does not work: + configuration.getAppenders().put(name, appender); + return appender; + } + + public AppenderSet getAppenderSet() { + return appenderSet; + } + + public Configuration getConfiguration() { + return configuration; + } + + public String getName() { + return name; + } + + public AbstractScript getScript() { + return script; + } + + public Builder withAppenderNodeSet(@SuppressWarnings("hiding") final AppenderSet appenderSet) { + this.appenderSet = appenderSet; + return this; + } + + public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + this.configuration = configuration; + return this; + } + + public Builder withName(@SuppressWarnings("hiding") final String name) { + this.name = name; + return this; + } + + public Builder withScript(@SuppressWarnings("hiding") final AbstractScript script) { + this.script = script; + return this; + } + + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + private ScriptSelector(final String name, final Filter filter, final Layout<? extends Serializable> layout) { + super(name, filter, layout); + } + + @Override + public void append(final LogEvent event) { + // Do nothing: This appender is only used to discover and build another appender + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/79d9e113/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptSelectorAppenderTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptSelectorAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptSelectorAppenderTest.java new file mode 100644 index 0000000..3a037c0 --- /dev/null +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptSelectorAppenderTest.java @@ -0,0 +1,95 @@ +/* + * 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; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * + */ +@RunWith(Parameterized.class) +public class ScriptSelectorAppenderTest { + + @Parameterized.Parameters(name = "{0} {1}") + public static Object[][] getParameters() { + // @formatter:off + return new Object[][] { + // { "log4j-appender-selector-groovy.xml" }, + { "log4j-appender-selector-javascript.xml" }, + }; + // @formatter:on + } + + @Rule + public final LoggerContextRule loggerContextRule; + + public ScriptSelectorAppenderTest(final String configLocation) { + this.loggerContextRule = new LoggerContextRule(configLocation); + } + + private ListAppender getListAppender() { + return loggerContextRule.getListAppender("List2"); + } + + private void logAndCheck() { + Marker marker = MarkerManager.getMarker("HEXDUMP"); + final Logger logger = loggerContextRule.getLogger(ScriptSelectorAppenderTest.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); + logger.error(marker, "DEADBEEF"); + assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3); + } + + @Test(expected = AssertionError.class) + public void testAppenderAbsence() { + loggerContextRule.getListAppender("List1"); + } + + @Test + public void testAppenderPresence() { + getListAppender(); + } + + @Test + public void testLogging1() { + logAndCheck(); + } + + @Test + public void testLogging2() { + logAndCheck(); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/79d9e113/log4j-core/src/test/resources/log4j-appender-selector-javascript.xml ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/resources/log4j-appender-selector-javascript.xml b/log4j-core/src/test/resources/log4j-appender-selector-javascript.xml new file mode 100644 index 0000000..f45e43c --- /dev/null +++ b/log4j-core/src/test/resources/log4j-appender-selector-javascript.xml @@ -0,0 +1,35 @@ +<?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> + <ScriptSelector name="SelectIt"> + <Script language="JavaScript"><![CDATA[ + "OSNameFoo".search("Foo") > -1 ? "List2" : "List1";]]> + </Script> + <AppenderSet> + <List name="List1" /> + <List name="List2" /> + </AppenderSet> + </ScriptSelector> + </Appenders> + <Loggers> + <Root level="error"> + <AppenderRef ref="SelectIt" /> + </Root> + </Loggers> +</Configuration> \ No newline at end of file