This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch merge-server in repository https://gitbox.apache.org/repos/asf/logging-log4j-samples.git
commit 37c156c5b4cfdc0771e51538dd185a7b782b8710 Author: Ralph Goers <[email protected]> AuthorDate: Mon Nov 28 22:44:16 2022 -0700 Restore files --- log4j-server/pom.xml | 242 +++++++++++++++ .../log4j/server/AbstractLogEventBridge.java | 44 +++ .../logging/log4j/server/AbstractSocketServer.java | 217 +++++++++++++ .../log4j/server/InputStreamLogEventBridge.java | 108 +++++++ .../org/apache/logging/log4j/server/JmsServer.java | 149 +++++++++ .../server/JsonInputStreamLogEventBridge.java | 89 ++++++ .../logging/log4j/server/LogEventBridge.java | 56 ++++ .../server/ObjectInputStreamLogEventBridge.java | 69 +++++ .../log4j/server/SecureTcpSocketServer.java | 41 +++ .../logging/log4j/server/TcpSocketServer.java | 341 +++++++++++++++++++++ .../logging/log4j/server/UdpSocketServer.java | 167 ++++++++++ .../log4j/server/XmlInputStreamLogEventBridge.java | 54 ++++ .../log4j/server/mom/jms/AbstractJmsReceiver.java | 69 +++++ .../log4j/server/mom/jms/JmsQueueReceiver.java | 46 +++ .../log4j/server/mom/jms/JmsTopicReceiver.java | 46 +++ .../logging/log4j/server/mom/jms/package-info.java | 26 ++ .../apache/logging/log4j/server/package-info.java | 24 ++ log4j-server/src/site/markdown/index.md | 30 ++ log4j-server/src/site/site.xml | 52 ++++ .../log4j/server/AbstractSocketServerTest.java | 240 +++++++++++++++ .../log4j/server/SslXmlSocketServerTest.java | 103 +++++++ .../log4j/server/TcpJsonSocketServerTest.java | 62 ++++ .../log4j/server/TcpXmlSocketServerTest.java | 65 ++++ .../logging/log4j/server/ThreadIdFilter.java | 40 +++ .../logging/log4j/server/ThreadNameFilter.java | 39 +++ .../logging/log4j/server/ThreadPriorityFilter.java | 40 +++ .../log4j/server/UdpJsonSocketServerTest.java | 58 ++++ .../log4j/server/UdpXmlSocketServerTest.java | 61 ++++ .../mom/activemq/ActiveMqBrokerServiceHelper.java | 51 +++ .../mom/activemq/ActiveMqBrokerServiceRule.java | 94 ++++++ .../log4j/server/mom/jms/JmsQueueReceiverTest.java | 42 +++ .../log4j/core/net/ssl/client.log4j2-keystore.jks | Bin 0 -> 6829 bytes .../logging/log4j/core/net/ssl/truststore.jks | Bin 0 -> 1487 bytes 33 files changed, 2765 insertions(+) diff --git a/log4j-server/pom.xml b/log4j-server/pom.xml new file mode 100644 index 0000000..5a78828 --- /dev/null +++ b/log4j-server/pom.xml @@ -0,0 +1,242 @@ +<?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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-tools</artifactId> + <version>3.0.0-SNAPSHOT</version> + <relativePath>../</relativePath> + </parent> + <artifactId>log4j-server</artifactId> + <packaging>jar</packaging> + <name>Apache Log4j Server components</name> + <description>The Apache Log4j server components</description> + <properties> + <log4jParentDir>${basedir}/..</log4jParentDir> + <projectDir>/log4j-server</projectDir> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-jms</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-jackson-json</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-jackson-xml</artifactId> + <version>${project.version}</version> + </dependency> + <!-- Used for JMS server (needs an implementation of course) --> + <dependency> + <groupId>org.jboss.spec.javax.jms</groupId> + <artifactId>jboss-jms-api_1.1_spec</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> + <!-- Required for JSON support --> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <!-- Required for XML support --> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + </dependency> + <!-- POM for jackson-dataformat-xml 2.8.3 depends on woodstox-core 5.0.2 --> + <dependency> + <groupId>com.fasterxml.woodstox</groupId> + <artifactId>woodstox-core</artifactId> + <version>5.0.2</version> + </dependency> + + <!-- TEST DEPENDENCIES --> + + <!-- Pull in useful test classes from API --> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.activemq</groupId> + <artifactId>activemq-broker</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <!-- Include the standard NOTICE and LICENSE --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-remote-resources-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>process</goal> + </goals> + <configuration> + <skip>false</skip> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Export-Package>org.apache.logging.log4j.core.net.*</Export-Package> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>test-jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <reporting> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-changes-plugin</artifactId> + <version>${changes.plugin.version}</version> + <reportSets> + <reportSet> + <reports> + <report>changes-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate> + <useJql>true</useJql> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>${checkstyle.plugin.version}</version> + <configuration> + <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> --> + <configLocation>${log4jParentDir}/checkstyle.xml</configLocation> + <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation> + <enableRulesSummary>false</enableRulesSummary> + <propertyExpansion>basedir=${basedir}</propertyExpansion> + <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>${javadoc.plugin.version}</version> + <configuration> + <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> + Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, + and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom> + <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating + project --> + <detectOfflineLinks>false</detectOfflineLinks> + <linksource>true</linksource> + </configuration> + <reportSets> + <reportSet> + <id>non-aggregate</id> + <reports> + <report>javadoc</report> + </reports> + </reportSet> + </reportSets> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>findbugs-maven-plugin</artifactId> + <version>${findbugs.plugin.version}</version> + <configuration> + <fork>true</fork> + <jvmArgs>-Duser.language=en</jvmArgs> + <threshold>Normal</threshold> + <effort>Default</effort> + <excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jxr-plugin</artifactId> + <version>${jxr.plugin.version}</version> + <reportSets> + <reportSet> + <id>non-aggregate</id> + <reports> + <report>jxr</report> + </reports> + </reportSet> + <reportSet> + <id>aggregate</id> + <reports> + <report>aggregate</report> + </reports> + </reportSet> + </reportSets> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <version>${pmd.plugin.version}</version> + <configuration> + <targetJdk>${maven.compiler.target}</targetJdk> + </configuration> + </plugin> + </plugins> + </reporting> +</project> + diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractLogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractLogEventBridge.java new file mode 100644 index 0000000..5e368cb --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractLogEventBridge.java @@ -0,0 +1,44 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Abstract class for implementations of {@link LogEventBridge}. + * + * @param <T> + * The kind of input stream read + */ +public abstract class AbstractLogEventBridge<T extends InputStream> implements LogEventBridge<T> { + + protected static final int END = -1; + + protected static final Logger logger = StatusLogger.getLogger(); + + // The default is to return the same object as given. + @SuppressWarnings("unchecked") + @Override + public T wrapStream(final InputStream inputStream) throws IOException { + return (T) inputStream; + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractSocketServer.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractSocketServer.java new file mode 100644 index 0000000..2024b6b --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/AbstractSocketServer.java @@ -0,0 +1,217 @@ +/* + * 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.server; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEventListener; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory; +import org.apache.logging.log4j.core.tools.BasicCommandLineArguments; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option; +import org.apache.logging.log4j.core.util.Log4jThread; +import org.apache.logging.log4j.util.Strings; + +/** + * Abstract socket server for TCP and UDP implementations. + * + * @param <T> + * The kind of input stream read + * + * TODO Make a LifeCycle + */ +public abstract class AbstractSocketServer<T extends InputStream> extends LogEventListener implements Runnable { + + protected static class CommandLineArguments extends BasicCommandLineArguments { + + @Option(names = { "--config", "-c" }, description = "Log4j configuration file location (path or URL).") + private String configLocation; + + @Option(names = { "--interactive", "-i" }, description = "Accepts commands on standard input (\"exit\" is the only command).") + private boolean interactive; + + @Option(names = { "--port", "-p" }, description = "The server port number, or 0 to automatically allocate a port number.") + private int port; + + @Option(names = { "--localbindaddress", "-a" }, description = "Server socket local bind address.") + private InetAddress localBindAddress; + + @Option(names = {"--classes", "-C"}, description = "Additional classes to allow deserialization") + private List<String> allowedClasses; + + String getConfigLocation() { + return configLocation; + } + + int getPort() { + return port; + } + + protected boolean isInteractive() { + return interactive; + } + + void setConfigLocation(final String configLocation) { + this.configLocation = configLocation; + } + + void setInteractive(final boolean interactive) { + this.interactive = interactive; + } + + void setPort(final int port) { + this.port = port; + } + + InetAddress getLocalBindAddress() { + return localBindAddress; + } + + void setLocalBindAddress(final InetAddress localBindAddress) { + this.localBindAddress = localBindAddress; + } + + List<String> getAllowedClasses() { + return allowedClasses == null ? Collections.<String>emptyList() : allowedClasses; + } + + void setAllowedClasses(final List<String> allowedClasses) { + this.allowedClasses = allowedClasses; + } + } + + /** + * Factory that creates a Configuration for the server. + */ + protected static class ServerConfigurationFactory extends XmlConfigurationFactory { + + private final String path; + + public ServerConfigurationFactory(final String path) { + this.path = path; + } + + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final String name, + final URI configLocation) { + if (Strings.isNotEmpty(path)) { + File file = null; + ConfigurationSource source = null; + try { + file = new File(path); + final FileInputStream is = new FileInputStream(file); + source = new ConfigurationSource(is, file); + } catch (final FileNotFoundException ignored) { + // Ignore this error + } + if (source == null) { + try { + final URL url = new URL(path); + source = new ConfigurationSource(url.openStream(), url); + } catch (final IOException ignored) { + // Ignore this error + } + } + + try { + if (source != null) { + return new XmlConfiguration(loggerContext, source); + } + } catch (final Exception ignored) { + // Ignore this error. + } + System.err.println("Unable to process configuration at " + path + ", using default."); + } + return super.getConfiguration(loggerContext, name, configLocation); + } + } + + protected static final int MAX_PORT = 65534; + + private volatile boolean active = true; + + protected final LogEventBridge<T> logEventInput; + + protected final Logger logger; + + /** + * Creates a new socket server. + * + * @param port + * listen to this port + * @param logEventInput + * Use this input to read log events. + */ + public AbstractSocketServer(final int port, final LogEventBridge<T> logEventInput) { + this.logger = LogManager.getLogger(this.getClass().getName() + '.' + port); + this.logEventInput = Objects.requireNonNull(logEventInput, "LogEventInput"); + } + + protected boolean isActive() { + return this.active; + } + + protected void setActive(final boolean isActive) { + this.active = isActive; + } + + /** + * Start this server in a new thread. + * + * @return the new thread that running this server. + */ + public Thread startNewThread() { + final Thread thread = new Log4jThread(this); + thread.start(); + return thread; + } + + public abstract void shutdown() throws Exception; + + public void awaitTermination(final Thread serverThread) throws Exception { + final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + while (true) { + final String line = reader.readLine(); + if (line == null + || line.equalsIgnoreCase("quit") + || line.equalsIgnoreCase("stop") + || line.equalsIgnoreCase("exit")) { + this.shutdown(); + serverThread.join(); + break; + } + } + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/InputStreamLogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/InputStreamLogEventBridge.java new file mode 100644 index 0000000..6d66303 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/InputStreamLogEventBridge.java @@ -0,0 +1,108 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LogEventListener; +import org.apache.logging.log4j.core.parser.ParseException; +import org.apache.logging.log4j.core.parser.TextLogEventParser; +import org.apache.logging.log4j.util.Strings; + +/** + * Reads and logs {@link LogEvent}s from an {@link InputStream}. + */ +public abstract class InputStreamLogEventBridge extends AbstractLogEventBridge<InputStream> { + + private final int bufferSize; + + private final Charset charset; + + private final String eventEndMarker; + + private final TextLogEventParser parser; + + public InputStreamLogEventBridge(final TextLogEventParser parser, final int bufferSize, final Charset charset, final String eventEndMarker) { + this.bufferSize = bufferSize; + this.charset = charset; + this.eventEndMarker = eventEndMarker; + this.parser = parser; + } + + abstract protected int[] getEventIndices(final String text, int beginIndex); + + @Override + public void logEvents(final InputStream inputStream, final LogEventListener logEventListener) + throws IOException, ParseException { + String workingText = Strings.EMPTY; + try { + // Allocate buffer once + final byte[] buffer = new byte[bufferSize]; + String textRemains = workingText = Strings.EMPTY; + while (true) { + // Process until the stream is EOF. + final int streamReadLength = inputStream.read(buffer); + if (streamReadLength == END) { + // The input stream is EOF + break; + } + final String text = workingText = textRemains + new String(buffer, 0, streamReadLength, charset); + int beginIndex = 0; + while (true) { + // Extract and log all XML events in the buffer + final int[] pair = getEventIndices(text, beginIndex); + final int eventStartMarkerIndex = pair[0]; + if (eventStartMarkerIndex < 0) { + // No more events or partial XML only in the buffer. + // Save the unprocessed string part + textRemains = text.substring(beginIndex); + break; + } + final int eventEndMarkerIndex = pair[1]; + if (eventEndMarkerIndex > 0) { + final int eventEndXmlIndex = eventEndMarkerIndex + eventEndMarker.length(); + final String textEvent = workingText = text.substring(eventStartMarkerIndex, eventEndXmlIndex); + final LogEvent logEvent = unmarshal(textEvent); + logEventListener.log(logEvent); + beginIndex = eventEndXmlIndex; + } else { + // No more events or partial XML only in the buffer. + // Save the unprocessed string part + textRemains = text.substring(beginIndex); + break; + } + } + } + } catch (final IOException ex) { + logger.error(workingText, ex); + } + } + + protected LogEvent unmarshal(final String jsonEvent) throws ParseException { + return this.parser.parseFrom(jsonEvent); + } + + @Override + public String toString() { + return "InputStreamLogEventBridge [bufferSize=" + bufferSize + ", charset=" + charset + ", eventEndMarker=" + + eventEndMarker + ", parser=" + parser + "]"; + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/JmsServer.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/JmsServer.java new file mode 100644 index 0000000..e46632c --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/JmsServer.java @@ -0,0 +1,149 @@ +/* + * 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.server; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.ObjectMessage; + +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.AbstractLifeCycle; +import org.apache.logging.log4j.core.LifeCycle2; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LogEventListener; +import org.apache.logging.log4j.core.net.JndiManager; +import org.apache.logging.log4j.jms.appender.JmsAppender; +import org.apache.logging.log4j.jms.appender.JmsManager; + +/** + * LogEventListener server that receives LogEvents over a JMS {@link javax.jms.Destination}. + * + * @since 2.1 + */ +public class JmsServer extends LogEventListener implements MessageListener, LifeCycle2 { + + private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED); + private final JmsManager jmsManager; + private MessageConsumer messageConsumer; + + public JmsServer(final String connectionFactoryBindingName, final String connectionFactoryName, + final String providerURL, final String destinationBindingName, final String username, final char[] password, + final Properties jndiProperties) { + final String managerName = JmsServer.class.getName() + '@' + JmsServer.class.hashCode(); + final Properties jndiManager = JndiManager.createProperties(connectionFactoryBindingName, providerURL, null, + null, null, jndiProperties); + jmsManager = JmsManager.getJmsManager(managerName, jndiManager, connectionFactoryName, destinationBindingName, + username, password, false, JmsAppender.Builder.DEFAULT_RECONNECT_INTERVAL_MILLIS); + } + + @Override + public State getState() { + return state.get(); + } + + @Override + public void onMessage(final Message message) { + try { + if (message instanceof ObjectMessage) { + final Object body = ((ObjectMessage) message).getObject(); + if (body instanceof LogEvent) { + log((LogEvent) body); + } else { + LOGGER.warn("Expected ObjectMessage to contain LogEvent. Got type {} instead.", body.getClass()); + } + } else { + LOGGER.warn("Received message of type {} and JMSType {} which cannot be handled.", message.getClass(), + message.getJMSType()); + } + } catch (final JMSException e) { + LOGGER.catching(e); + } + } + + @Override + public void initialize() { + } + + @Override + public void start() { + if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { + try { + messageConsumer = jmsManager.createMessageConsumer(); + messageConsumer.setMessageListener(this); + } catch (final JMSException e) { + throw new LoggingException(e); + } + } + } + + @Override + public void stop() { + stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + boolean stopped = true; + try { + messageConsumer.close(); + } catch (final JMSException e) { + LOGGER.debug("Exception closing {}", messageConsumer, e); + stopped = false; + } + return stopped && jmsManager.stop(timeout, timeUnit); + } + + @Override + public boolean isStarted() { + return state.get() == State.STARTED; + } + + @Override + public boolean isStopped() { + return state.get() == State.STOPPED; + } + + /** + * Starts and runs this server until the user types "exit" into standard input. + * + * @throws IOException + */ + public void commandLineLoop() throws IOException { + System.out.println("Type \"exit\" to quit."); + final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset())); + while (true) { + final String line = stdin.readLine(); + if (line == null || line.equalsIgnoreCase("exit")) { + System.out.println("Exiting. Kill the application if it does not exit due to daemon threads."); + this.stop(); + return; + } + } + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/JsonInputStreamLogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/JsonInputStreamLogEventBridge.java new file mode 100644 index 0000000..8444c74 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/JsonInputStreamLogEventBridge.java @@ -0,0 +1,89 @@ +/* + * 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.server; + +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.jackson.json.parser.JsonLogEventParser; +import org.apache.logging.log4j.util.Chars; + +/** + * Reads and logs JSON {@link LogEvent}s from an {@link InputStream}.. + */ +public class JsonInputStreamLogEventBridge extends InputStreamLogEventBridge { + + private static final int[] END_PAIR = new int[] { END, END }; + private static final char EVENT_END_MARKER = '}'; + private static final char EVENT_START_MARKER = '{'; + private static final char JSON_ESC = '\\'; + private static final char JSON_STR_DELIM = Chars.DQUOTE; + + public JsonInputStreamLogEventBridge() { + this(1024, Charset.defaultCharset()); + } + + public JsonInputStreamLogEventBridge(final int bufferSize, final Charset charset) { + super(new JsonLogEventParser(), bufferSize, charset, + String.valueOf(EVENT_END_MARKER)); + } + + @Override + protected int[] getEventIndices(final String text, final int beginIndex) { + // Scan the text for the end of the next JSON object. + final int start = text.indexOf(EVENT_START_MARKER, beginIndex); + if (start == END) { + return END_PAIR; + } + final char[] charArray = text.toCharArray(); + int stack = 0; + boolean inStr = false; + boolean inEsc = false; + for (int i = start; i < charArray.length; i++) { + final char c = charArray[i]; + if (inEsc) { + // Skip this char and continue + inEsc = false; + } else { + switch (c) { + case EVENT_START_MARKER: + if (!inStr) { + stack++; + } + break; + case EVENT_END_MARKER: + if (!inStr) { + stack--; + } + break; + case JSON_STR_DELIM: + inStr = !inStr; + break; + case JSON_ESC: + inEsc = true; + break; + } + if (stack == 0) { + return new int[] { start, i }; + } + } + } + return END_PAIR; + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/LogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/LogEventBridge.java new file mode 100644 index 0000000..7c915fd --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/LogEventBridge.java @@ -0,0 +1,56 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LogEventListener; +import org.apache.logging.log4j.core.parser.ParseException; + +/** + * Reads {@link LogEvent}s from the given input stream and logs them as they are discovered on the given logger. + * + * <p> + * Should be stateless. + * </p> + * + * @param <T> + * The kind of {@link InputStream} to wrap and read. + */ +public interface LogEventBridge<T extends InputStream> { + + /** + * Reads {@link LogEvent}s from the given input stream and logs them as they are discovered on the given logger. + * + * @param inputStream + * the input stream to read + * @param logEventListener + * TODO + */ + void logEvents(T inputStream, LogEventListener logEventListener) throws IOException, ParseException; + + /** + * Wraps the given stream if needed. + * + * @param inputStream + * the stream to wrap + * @return the wrapped stream or the given stream. + */ + T wrapStream(InputStream inputStream) throws IOException; +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/ObjectInputStreamLogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/ObjectInputStreamLogEventBridge.java new file mode 100644 index 0000000..c5ab4eb --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/ObjectInputStreamLogEventBridge.java @@ -0,0 +1,69 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.Collections; +import java.util.List; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LogEventListener; +import org.apache.logging.log4j.util.FilteredObjectInputStream; +import org.apache.logging.log4j.core.layout.SerializedLayout; + +/** + * Reads and logs serialized {@link LogEvent} objects (created with {@link SerializedLayout}) from an {@link ObjectInputStream}. + * + * @deprecated Java Serialization has inherent security weaknesses, see https://www.owasp.org/index.php/Deserialization_of_untrusted_data . + * Therefore {@link SerializedLayout} is deprecated, and so is this class. We recommend using {@link JsonInputStreamLogEventBridge} instead. + */ +@Deprecated +public class ObjectInputStreamLogEventBridge extends AbstractLogEventBridge<ObjectInputStream> { + + private final List<String> allowedClasses; + + public ObjectInputStreamLogEventBridge() { + this(Collections.<String>emptyList()); + } + + /** + * Constructs an ObjectInputStreamLogEventBridge with additional allowed classes to deserialize. + * + * @param allowedClasses class names to also allow for deserialization + * @since 2.8.2 + */ + public ObjectInputStreamLogEventBridge(final List<String> allowedClasses) { + this.allowedClasses = allowedClasses; + } + + @Override + public void logEvents(final ObjectInputStream inputStream, final LogEventListener logEventListener) + throws IOException { + try { + logEventListener.log((LogEvent) inputStream.readObject()); + } catch (final ClassNotFoundException e) { + throw new IOException(e); + } + } + + @Override + public ObjectInputStream wrapStream(final InputStream inputStream) throws IOException { + return new FilteredObjectInputStream(inputStream, allowedClasses); + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/SecureTcpSocketServer.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/SecureTcpSocketServer.java new file mode 100644 index 0000000..39f2afc --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/SecureTcpSocketServer.java @@ -0,0 +1,41 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; + +/** + * Listens for events over a secure socket connection (SSL/TLS). + * + * @param <T> + * The kind of input stream read + */ +public class SecureTcpSocketServer<T extends InputStream> extends TcpSocketServer<T> { + + public static SecureTcpSocketServer<InputStream> createJsonServer(final int port, final SslConfiguration sslConfiguration) throws IOException { + return new SecureTcpSocketServer<>(port, new JsonInputStreamLogEventBridge(), sslConfiguration); + } + + public SecureTcpSocketServer(final int port, final LogEventBridge<T> logEventInput, + final SslConfiguration sslConfig) throws IOException { + super(port, logEventInput, sslConfig.getSslServerSocketFactory().createServerSocket(port)); + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/TcpSocketServer.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/TcpSocketServer.java new file mode 100644 index 0000000..c3f5ae7 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/TcpSocketServer.java @@ -0,0 +1,341 @@ +/* + * 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.server; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.OptionalDataException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.parser.ParseException; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.Log4jThread; +import org.apache.logging.log4j.core.tools.picocli.CommandLine; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option; +import org.apache.logging.log4j.message.EntryMessage; + +/** + * Listens for Log4j events on a TCP server socket and passes them on to Log4j. + * + * @param <T> + * The kind of input stream read + * @see #main(String[]) + */ +public class TcpSocketServer<T extends InputStream> extends AbstractSocketServer<T> { + + @Command(name = "TcpSocketServer") + protected static class CommandLineArguments extends AbstractSocketServer.CommandLineArguments { + + @Option(names = { "--backlog", "-b" }, description = "Server socket backlog. Must be a positive integer.") + // Same default as ServerSocket + private int backlog = 50; + + int getBacklog() { + return backlog; + } + + void setBacklog(final int backlog) { + this.backlog = backlog; + } + } + + /** + * Thread that processes the events. + */ + private class SocketHandler extends Log4jThread { + + private final T inputStream; + private final Socket socket; + + private volatile boolean shutdown = false; + + public SocketHandler(final Socket socket) throws IOException { + this.socket = socket; + this.inputStream = logEventInput.wrapStream(socket.getInputStream()); + } + + @Override + public void run() { + final EntryMessage entry = logger.traceEntry(); + boolean closed = false; + try { + try { + while (!shutdown) { + logEventInput.logEvents(inputStream, TcpSocketServer.this); + } + } catch (final EOFException e) { + closed = true; + } catch (final OptionalDataException e) { + logger.error("OptionalDataException eof=" + e.eof + " length=" + e.length, e); + } catch (final IOException e) { + logger.error("IOException encountered while reading from socket", e); + } catch (ParseException e) { + logger.error("ParseException encountered while reading from socket", e); + } + if (!closed) { + Closer.closeSilently(inputStream); + } + } finally { + handlers.remove(Long.valueOf(getId())); + } + logger.traceExit(entry); + } + + public void shutdown() { + this.shutdown = true; + if (socket != null) { + Closer.closeSilently(socket); + } + interrupt(); + } + } + + /** + * Creates a socket server that reads JSON log events. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + public static TcpSocketServer<InputStream> createJsonSocketServer(final int port) throws IOException { + LOGGER.entry("createJsonSocketServer", port); + final TcpSocketServer<InputStream> socketServer = new TcpSocketServer<>(port, new JsonInputStreamLogEventBridge()); + return LOGGER.exit(socketServer); + } + + /** + * Creates a socket server that reads JSON log events. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @param backlog + * The server socket backlog. + * @param localBindAddress + * The local InetAddress the server will bind to + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + * @since 2.9 + */ + public static TcpSocketServer<InputStream> createJsonSocketServer(final int port, final int backlog, + final InetAddress localBindAddress) throws IOException { + LOGGER.entry("createJsonSocketServer", port, backlog, localBindAddress); + final TcpSocketServer<InputStream> socketServer = new TcpSocketServer<>(port, backlog, localBindAddress, + new JsonInputStreamLogEventBridge()); + return LOGGER.exit(socketServer); + } + + /** + * Creates a socket server that reads XML log events. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + public static TcpSocketServer<InputStream> createXmlSocketServer(final int port) throws IOException { + LOGGER.entry(port); + final TcpSocketServer<InputStream> socketServer = new TcpSocketServer<>(port, new XmlInputStreamLogEventBridge()); + return LOGGER.exit(socketServer); + } + + /** + * Creates a socket server that reads XML log events. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @param backlog + * The server socket backlog. + * @param localBindAddress + * The local InetAddress the server will bind to + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + * @since 2.9 + */ + public static TcpSocketServer<InputStream> createXmlSocketServer(final int port, + final int backlog, final InetAddress localBindAddress + ) throws IOException { + LOGGER.entry(port); + final TcpSocketServer<InputStream> socketServer = new TcpSocketServer<>(port, backlog, localBindAddress, + new XmlInputStreamLogEventBridge()); + return LOGGER.exit(socketServer); + } + + /** + * Main startup for the server. Run with "--help" for to print command line help on the console. + * + * @param args + * The command line arguments. + * @throws Exception + * if an error occurs. + */ + public static void main(final String[] args) throws Exception { + CommandLineArguments cla = CommandLine.populateCommand(new CommandLineArguments(), args); + if (cla.isHelp() || cla.backlog < 0 || cla.getPort() < 0) { + CommandLine.usage(cla, System.err); + return; + } + if (cla.getConfigLocation() != null) { + ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation())); + } + final TcpSocketServer<InputStream> socketServer = TcpSocketServer.createJsonSocketServer( + cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress()); + final Thread serverThread = socketServer.startNewThread(); + if (cla.isInteractive()) { + socketServer.awaitTermination(serverThread); + } + } + + private final ConcurrentMap<Long, SocketHandler> handlers = new ConcurrentHashMap<>(); + + private final ServerSocket serverSocket; + + /** + * Constructor. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @param backlog + * The server socket backlog. + * @param localBindAddress + * The local InetAddress the server will bind to + * @param logEventInput + * the log even input + * @throws IOException + * if an I/O error occurs when opening the socket. + * @since 2.7 + */ + @SuppressWarnings("resource") + public TcpSocketServer(final int port, final int backlog, final InetAddress localBindAddress, final LogEventBridge<T> logEventInput) throws IOException { + this(port, logEventInput, new ServerSocket(port, backlog, localBindAddress)); + } + + /** + * Constructor. + * + * @param port + * The port number, or 0 to automatically allocate a port number. + * @param logEventInput + * the log even input + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + @SuppressWarnings("resource") + public TcpSocketServer(final int port, final LogEventBridge<T> logEventInput) throws IOException { + this(port, logEventInput, new ServerSocket(port)); + } + + /** + * Constructor. + * + * @param port + * to listen. + * @param logEventInput + * the log even input + * @param serverSocket + * the socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + public TcpSocketServer(final int port, final LogEventBridge<T> logEventInput, final ServerSocket serverSocket) + throws IOException { + super(port, logEventInput); + this.serverSocket = serverSocket; + } + + /** + * Accept incoming events and processes them. + */ + @Override + public void run() { + final EntryMessage entry = logger.traceEntry(); + while (isActive()) { + if (serverSocket.isClosed()) { + return; + } + try { + // Accept incoming connections. + logger.debug("Listening for a connection {}...", serverSocket); + @SuppressWarnings("resource") // clientSocket is closed during SocketHandler shutdown + final Socket clientSocket = serverSocket.accept(); + logger.debug("Accepted connection on {}...", serverSocket); + logger.debug("Socket accepted: {}", clientSocket); + clientSocket.setSoLinger(true, 0); + + // accept() will block until a client connects to the server. + // If execution reaches this point, then it means that a client + // socket has been accepted. + + final SocketHandler handler = new SocketHandler(clientSocket); + handlers.put(Long.valueOf(handler.getId()), handler); + handler.start(); + } catch (final IOException e) { + if (serverSocket.isClosed()) { + // OK we're done. + logger.traceExit(entry); + return; + } + logger.error("Exception encountered on accept. Ignoring. Stack trace :", e); + } + } + for (final Map.Entry<Long, SocketHandler> handlerEntry : handlers.entrySet()) { + final SocketHandler handler = handlerEntry.getValue(); + handler.shutdown(); + try { + handler.join(); + } catch (final InterruptedException ignored) { + // Ignore the exception + } + } + logger.traceExit(entry); + } + + /** + * Shutdown the server. + * + * @throws IOException if the server socket could not be closed + */ + @Override + public void shutdown() throws IOException { + final EntryMessage entry = logger.traceEntry(); + setActive(false); + //Thread.currentThread().interrupt(); + serverSocket.close(); + logger.traceExit(entry); + } + + @Override + public String toString() { + return "TcpSocketServer [serverSocket=" + serverSocket + ", handlers=" + handlers + ", logEventInput=" + + logEventInput + "]"; + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/UdpSocketServer.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/UdpSocketServer.java new file mode 100644 index 0000000..6148ad8 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/UdpSocketServer.java @@ -0,0 +1,167 @@ +/* + * 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.server; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.OptionalDataException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.util.List; + +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.parser.ParseException; +import org.apache.logging.log4j.core.tools.picocli.CommandLine; + +/** + * Listens for Log4j events on a datagram socket and passes them on to Log4j. + * + * @param <T> + * The kind of input stream read + * @see #main(String[]) + */ +public class UdpSocketServer<T extends InputStream> extends AbstractSocketServer<T> { + + /** + * Creates a socket server that reads JSON log events. + * + * @param port + * the port to listen + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + public static UdpSocketServer<InputStream> createJsonSocketServer(final int port) throws IOException { + return new UdpSocketServer<>(port, new JsonInputStreamLogEventBridge()); + } + + /** + * Creates a socket server that reads XML log events. + * + * @param port + * the port to listen + * @return a new a socket server + * @throws IOException + * if an I/O error occurs when opening the socket. + */ + public static UdpSocketServer<InputStream> createXmlSocketServer(final int port) throws IOException { + return new UdpSocketServer<>(port, new XmlInputStreamLogEventBridge()); + } + + /** + * Main startup for the server. Run with "--help" for to print command line help on the console. + * + * @param args + * The command line arguments. + * @throws Exception + * if an error occurs. + */ + public static void main(final String[] args) throws Exception { + final CommandLineArguments cla = CommandLine.populateCommand(new CommandLineArguments(), args); + if (cla.isHelp() || cla.getPort() < 0) { + CommandLine.usage(cla, System.err); + return; + } + if (cla.getConfigLocation() != null) { + ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation())); + } + final UdpSocketServer<InputStream> socketServer = UdpSocketServer + .createJsonSocketServer(cla.getPort()); + final Thread serverThread = socketServer.startNewThread(); + if (cla.isInteractive()) { + socketServer.awaitTermination(serverThread); + } + } + + private final DatagramSocket datagramSocket; + + // max size so we only have to deal with one packet + private final int maxBufferSize = 1024 * 65 + 1024; + + /** + * Constructor. + * + * @param port + * to listen on. + * @param logEventInput + * @throws IOException + * If an error occurs. + */ + public UdpSocketServer(final int port, final LogEventBridge<T> logEventInput) throws IOException { + super(port, logEventInput); + this.datagramSocket = new DatagramSocket(port); + } + + /** + * Accept incoming events and processes them. + */ + @Override + public void run() { + while (isActive()) { + if (datagramSocket.isClosed()) { + // OK we're done. + return; + } + try { + final byte[] buf = new byte[maxBufferSize]; + final DatagramPacket packet = new DatagramPacket(buf, buf.length); + datagramSocket.receive(packet); + final ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength()); + logEventInput.logEvents(logEventInput.wrapStream(bais), this); + } catch (final OptionalDataException e) { + if (datagramSocket.isClosed()) { + // OK we're done. + return; + } + logger.error("OptionalDataException eof=" + e.eof + " length=" + e.length, e); + } catch (final EOFException e) { + if (datagramSocket.isClosed()) { + // OK we're done. + return; + } + logger.info("EOF encountered"); + } catch (final IOException e) { + if (datagramSocket.isClosed()) { + // OK we're done. + return; + } + logger.error("Exception encountered on accept. Ignoring. Stack Trace :", e); + } catch (ParseException e) { + logger.error("Unable to parse log event", e); + } + } + } + + /** + * Shutdown the server. + */ + @Override + public void shutdown() { + this.setActive(false); + //Thread.currentThread().interrupt(); + datagramSocket.close(); + } + + @Override + public String toString() { + return "UdpSocketServer [datagramSocket=" + datagramSocket + ", maxBufferSize=" + maxBufferSize + + ", logEventInput=" + logEventInput + "]"; + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/XmlInputStreamLogEventBridge.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/XmlInputStreamLogEventBridge.java new file mode 100644 index 0000000..3c4a1e0 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/XmlInputStreamLogEventBridge.java @@ -0,0 +1,54 @@ +/* + * 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.server; + +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.jackson.xml.parser.XmlLogEventParser; + +/** + * Reads and logs {@link LogEvent}s from an {@link InputStream}. + */ +public class XmlInputStreamLogEventBridge extends InputStreamLogEventBridge { + + private static final String EVENT_END = "</Event>"; + private static final String EVENT_START_NS_N = "<Event>"; + private static final String EVENT_START_NS_Y = "<Event "; + + public XmlInputStreamLogEventBridge() { + this(1024, Charset.defaultCharset()); + } + + public XmlInputStreamLogEventBridge(final int bufferSize, final Charset charset) { + super(new XmlLogEventParser(), bufferSize, charset, EVENT_END); + } + + @Override + protected int[] getEventIndices(final String text, final int beginIndex) { + int start = text.indexOf(EVENT_START_NS_Y, beginIndex); + int startLen = EVENT_START_NS_Y.length(); + if (start < 0) { + start = text.indexOf(EVENT_START_NS_N, beginIndex); + startLen = EVENT_START_NS_N.length(); + } + final int end = start < 0 ? -1 : text.indexOf(EVENT_END, start + startLen); + return new int[] { start, end }; + } + +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/AbstractJmsReceiver.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/AbstractJmsReceiver.java new file mode 100644 index 0000000..a85d2f6 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/AbstractJmsReceiver.java @@ -0,0 +1,69 @@ +/* + * 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.server.mom.jms; + +import java.util.Properties; + +import org.apache.logging.log4j.server.JmsServer; + +/** + * Common JMS server functionality. + * + * @since 2.6 + */ +public abstract class AbstractJmsReceiver { + + class CommandLineArgs { + + } + + /** + * Prints out usage information to {@linkplain System#err standard error}. + */ + protected abstract void usage(); + + /** + * Executes a JmsServer with the given command line arguments. + * + * @param interactive + * Whether or not this is an interactive application by providing a command line and exit on error. + * @param args + * command line arguments + * + * @throws Exception + */ + protected void doMain(boolean interactive, final String... args) throws Exception { + // TODO Too many args, Use picocli + if (args.length < 5) { + usage(); + if (interactive) { + System.exit(1); + } + } + final Properties properties = new Properties(); + for (int index = 5; index < args.length; index += 2) { + properties.put(args[index], args[index + 1]); + } + final JmsServer server = new JmsServer(args[0], "ConnectionFactory", args[1], args[2], args[3], + args[4].toCharArray(), properties); + server.start(); + if (interactive) { + server.commandLineLoop(); + } + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiver.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiver.java new file mode 100644 index 0000000..f7cadc8 --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiver.java @@ -0,0 +1,46 @@ +/* + * 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.server.mom.jms; + +/** + * Receives Log Events over a JMS Queue. This implementation expects that all messages will + * contain a serialized LogEvent. + */ +public class JmsQueueReceiver extends AbstractJmsReceiver { + + JmsQueueReceiver() { + // Usage is to call main() + } + + /** + * Main startup for the receiver. + * + * @param args The command line arguments. + * @throws Exception if an error occurs. + */ + public static void main(final String[] args) throws Exception { + new JmsQueueReceiver().doMain(true, args); + } + + @Override + protected void usage() { + System.err.println("Wrong number of arguments."); + System.err.println("Usage: java " + JmsQueueReceiver.class.getName() + + " QueueConnectionFactoryBindingName QueueBindingName username password [jndiPropertyKey jndiPropertyValue]*"); + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsTopicReceiver.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsTopicReceiver.java new file mode 100644 index 0000000..8e50cdf --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/JmsTopicReceiver.java @@ -0,0 +1,46 @@ +/* + * 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.server.mom.jms; + +/** + * Receives Topic messages that contain LogEvents. This implementation expects that all messages + * are serialized log events. + */ +public class JmsTopicReceiver extends AbstractJmsReceiver { + + private JmsTopicReceiver() { + // Usage is to call main() + } + + /** + * Main startup for the receiver. + * + * @param args The command line arguments. + * @throws Exception if an error occurs. + */ + public static void main(final String[] args) throws Exception { + new JmsTopicReceiver().doMain(true, args); + } + + @Override + protected void usage() { + System.err.println("Wrong number of arguments."); + System.err.println("Usage: java " + JmsTopicReceiver.class.getName() + + " TopicConnectionFactoryBindingName TopicBindingName username password [jndiPropertyKey jndiPropertyValue]*"); + } +} diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/package-info.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/package-info.java new file mode 100644 index 0000000..dd9c90e --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/mom/jms/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Supporting network code for JMS appenders. + * + * <p>Note that you can use JmsQueueReceiver or JmsTopicReceiver as executable main classes to receive log events over + * JMS (sent via the appropriate JMS appender) that can be subsequently logged according to the configuration given to + * the running process. Of course, use of these classes as standalone executables are entirely optional and can + * be used directly in your application (e.g., through your Spring {@code beans.xml} configuration).</p> + */ +package org.apache.logging.log4j.server.mom.jms; diff --git a/log4j-server/src/main/java/org/apache/logging/log4j/server/package-info.java b/log4j-server/src/main/java/org/apache/logging/log4j/server/package-info.java new file mode 100644 index 0000000..c446a9d --- /dev/null +++ b/log4j-server/src/main/java/org/apache/logging/log4j/server/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Standalone server classes for consuming log events over a network. Each of the various servers should be used with + * another Log4j configuration to handle incoming {@link org.apache.logging.log4j.core.LogEvent}s. It is recommended + * to consider using the <a href="../../../../../../../../../manual/appenders.html#FlumeAppender">Flume Appender</a> + * for highly reliable networked logging. + */ +package org.apache.logging.log4j.server; diff --git a/log4j-server/src/site/markdown/index.md b/log4j-server/src/site/markdown/index.md new file mode 100644 index 0000000..b3d96ba --- /dev/null +++ b/log4j-server/src/site/markdown/index.md @@ -0,0 +1,30 @@ +<!-- vim: set syn=markdown : --> +<!-- + 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. +--> + +# Log4j Server components + +## Log4j Server components + +Standalone server classes for consuming log events over a network. Each of the various servers should be used with +another Log4j configuration to handle incoming log events. It is recommended to consider using the +[Flume Appender](../manual/appenders.html#FlumeAppender) for highly reliable networked logging. + +## Requirements + +The Log4j Server components requires the Log4j 2 API and core. This component was introduced in Log4j 2.8.2, +before it was part of log4j-core. For more information, see [Runtime Dependencies](../manual/runtime-dependencies.html). diff --git a/log4j-server/src/site/site.xml b/log4j-server/src/site/site.xml new file mode 100644 index 0000000..f863dbf --- /dev/null +++ b/log4j-server/src/site/site.xml @@ -0,0 +1,52 @@ +<!-- + 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. + +--> +<project name="Log4j IOStreams" + xmlns="http://maven.apache.org/DECORATION/1.4.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd"> + <body> + <links> + <item name="Apache" href="http://www.apache.org/" /> + <item name="Logging Services" href="http://logging.apache.org/"/> + <item name="Log4j" href="../index.html"/> + </links> + + <!-- Component-specific reports --> + <menu ref="reports"/> + + <!-- Overall Project Info --> + <menu name="Log4j Project Information" img="icon-info-sign"> + <item name="Dependencies" href="../dependencies.html" /> + <item name="Dependency Convergence" href="../dependency-convergence.html" /> + <item name="Dependency Management" href="../dependency-management.html" /> + <item name="Project Team" href="../team-list.html" /> + <item name="Mailing Lists" href="../mail-lists.html" /> + <item name="Issue Tracking" href="../issue-tracking.html" /> + <item name="Project License" href="../license.html" /> + <item name="Source Repository" href="../source-repository.html" /> + <item name="Project Summary" href="../project-summary.html" /> + </menu> + + <menu name="Log4j Project Reports" img="icon-cog"> + <item name="Changes Report" href="../changes-report.html" /> + <item name="JIRA Report" href="../jira-report.html" /> + <item name="Surefire Report" href="../surefire-report.html" /> + <item name="RAT Report" href="../rat-report.html" /> + </menu> + </body> +</project> diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/AbstractSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/AbstractSocketServerTest.java new file mode 100644 index 0000000..758716b --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/AbstractSocketServerTest.java @@ -0,0 +1,240 @@ +/* + * 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.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Level; +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.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.SocketAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.jackson.json.layout.JsonLayout; +import org.apache.logging.log4j.jackson.xml.layout.XmlLayout; +import org.apache.logging.log4j.test.AvailablePortFinder; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.junit.After; +import org.junit.Ignore; +import org.junit.Test; + +/** + * + */ +public abstract class AbstractSocketServerTest { + + protected static Thread thread; + + private static final String MESSAGE = "This is test message"; + + private static final String MESSAGE_2 = "This is test message 2"; + + private static final String MESSAGE_WITH_SPECIAL_CHARS = "{This}\n[is]\"n\"a\"\r\ntrue:\n\ttest,\nmessage"; + + static final int PORT_NUM = AvailablePortFinder.getNextAvailable(); + + static final int PORT = PORT_NUM; + + private final LoggerContext ctx = LoggerContext.getContext(false); + + private final boolean expectLengthException; + + protected final int port; + + protected final Protocol protocol; + + private final Logger rootLogger = ctx.getLogger(AbstractSocketServerTest.class.getSimpleName()); + + protected AbstractSocketServerTest(final Protocol protocol, final int port, final boolean expectLengthException) { + this.protocol = protocol; + this.port = port; + this.expectLengthException = expectLengthException; + } + + protected Layout<String> createJsonLayout() { + // @formatter: off + return JsonLayout.newBuilder() + .setLocationInfo(true) + .setProperties(true) + .setPropertiesAsList(false) + .setComplete(false) + .setCompact(false) + .setEventEol(false) + .setIncludeStacktrace(true) + .build(); + // @formatter: on + + //return JsonLayout.createLayout(null, true, true, false, false, false, false, null, null, null, true); + } + + protected abstract Layout<? extends Serializable> createLayout(); + + protected Layout<String> createXmlLayout() { + return XmlLayout.newBuilder() + .setLocationInfo(true) + .setProperties(true) + .setComplete(false) + .setCompact(false) + .setIncludeStacktrace(true) + .build(); + } + + @After + public void tearDown() { + final Map<String, Appender> map = rootLogger.getAppenders(); + for (final Map.Entry<String, Appender> entry : map.entrySet()) { + final Appender appender = entry.getValue(); + rootLogger.removeAppender(appender); + appender.stop(); + } + } + + @Test + @Ignore("Broken test?") + public void test1000ShortMessages() throws Exception { + testServer(1000); + } + + @Test + @Ignore("Broken test?") + public void test100ShortMessages() throws Exception { + testServer(100); + } + + @Test + public void test10ShortMessages() throws Exception { + testServer(10); + } + + @Test + public void test1ShortMessages() throws Exception { + testServer(1); + } + + @Test + public void test2ShortMessages() throws Exception { + testServer(2); + } + + @Test + public void test64KBMessages() throws Exception { + final char[] a64K = new char[1024 * 64]; + Arrays.fill(a64K, 'a'); + final String m1 = new String(a64K); + final String m2 = MESSAGE_2 + m1; + if (expectLengthException) { + try { + testServer(m1, m2); + } catch (final AppenderLoggingException are) { + assertTrue("", are.getCause() != null && are.getCause() instanceof IOException); + // Failure expected. + } + } else { + testServer(m1, m2); + } + } + + + @Test + public void testMessagesWithSpecialChars() throws Exception { + testServer(MESSAGE_WITH_SPECIAL_CHARS); + } + + + private void testServer(final int size) throws Exception { + final String[] messages = new String[size]; + for (int i = 0; i < messages.length; i++) { + messages[i] = MESSAGE + " " + i; + } + testServer(messages); + } + + protected void testServer(final String... messages) throws Exception { + final Filter socketFilter = new ThreadNameFilter(Filter.Result.NEUTRAL, Filter.Result.DENY); + final Filter serverFilter = new ThreadNameFilter(Filter.Result.DENY, Filter.Result.NEUTRAL); + final Layout<? extends Serializable> socketLayout = createLayout(); + final SocketAppender socketAppender = createSocketAppender(socketFilter, socketLayout); + socketAppender.start(); + final ListAppender listAppender = new ListAppender("Events", serverFilter, null, false, false); + listAppender.start(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m %ex%n").build(); + final ConsoleAppender console = ConsoleAppender.createDefaultAppenderForLayout(layout); + final Logger serverLogger = ctx.getLogger(this.getClass().getName()); + serverLogger.addAppender(console); + serverLogger.setAdditive(false); + + // set appender on root and set level to debug + rootLogger.addAppender(socketAppender); + rootLogger.addAppender(listAppender); + rootLogger.setAdditive(false); + rootLogger.setLevel(Level.DEBUG); + for (final String message : messages) { + rootLogger.debug(message); + } + final int MAX_TRIES = 400; + for (int i = 0; i < MAX_TRIES; i++) { + if (listAppender.getEvents().size() < messages.length) { + try { + // Let the server-side read the messages. + Thread.sleep(50); + } catch (final Exception e) { + e.printStackTrace(); + } + } else { + break; + } + } + final List<LogEvent> events = listAppender.getEvents(); + assertNotNull("No event retrieved", events); + assertEquals("Incorrect number of events received", messages.length, events.size()); + for (int i = 0; i < messages.length; i++) { + assertTrue("Incorrect event", events.get(i).getMessage().getFormattedMessage().equals(messages[i])); + } + } + + protected SocketAppender createSocketAppender(final Filter socketFilter, + final Layout<? extends Serializable> socketLayout) { + // @formatter:off + return SocketAppender.newBuilder() + .setProtocol(this.protocol) + .setHost("localhost") + .setPort(this.port) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFlush(true) + .setImmediateFail(false) + .setIgnoreExceptions(false) + .setLayout(socketLayout).setFilter(socketFilter) + .build(); + // @formatter:on + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/SslXmlSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/SslXmlSocketServerTest.java new file mode 100644 index 0000000..6d08723 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/SslXmlSocketServerTest.java @@ -0,0 +1,103 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.SocketAppender; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.StoreConfigurationException; +import org.apache.logging.log4j.core.net.ssl.TestConstants; +import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class SslXmlSocketServerTest extends AbstractSocketServerTest { + + private static TcpSocketServer<InputStream> server; + + private static SslConfiguration sslConfiguration; + + private static void initServerSocketFactory() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, + TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, + TestConstants.TRUSTSTORE_PWD(), null, null); + sslConfiguration = SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + @Override + protected SocketAppender createSocketAppender(final Filter socketFilter, + final Layout<? extends Serializable> socketLayout) { + // @formatter:off + return SocketAppender.newBuilder() + .setProtocol(this.protocol) + .setHost("localhost") + .setPort(this.port) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFlush(true) + .setImmediateFail(false) + .setIgnoreExceptions(false) + .setLayout(socketLayout).setFilter(socketFilter) + .setSslConfiguration(sslConfiguration) + .build(); + // @formatter:on + } + + @BeforeClass + public static void setupClass() throws Exception { + LoggerContext.getContext(false).reconfigure(); + initServerSocketFactory(); + // Use a large buffer just to test the code, the UDP test uses a tiny buffer + server = new SecureTcpSocketServer<>(PORT_NUM, new XmlInputStreamLogEventBridge(1024 * 100, + Charset.defaultCharset()), sslConfiguration); + thread = server.startNewThread(); + } + + @AfterClass + public static void tearDownClass() { + try { + server.shutdown(); + } catch (final IOException e) { + e.printStackTrace(); + } + try { + thread.join(); + } catch (final InterruptedException e) { + // ignore + } + } + + public SslXmlSocketServerTest() { + super(Protocol.SSL, PORT, false); + } + + @Override + protected Layout<String> createLayout() { + return super.createXmlLayout(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpJsonSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpJsonSocketServerTest.java new file mode 100644 index 0000000..63470dd --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpJsonSocketServerTest.java @@ -0,0 +1,62 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.net.Protocol; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class TcpJsonSocketServerTest extends AbstractSocketServerTest { + + private static TcpSocketServer<InputStream> server; + + @BeforeClass + public static void setupClass() throws Exception { + LoggerContext.getContext(false).reconfigure(); + server = TcpSocketServer.createJsonSocketServer(PORT_NUM); + thread = server.startNewThread(); + } + + @AfterClass + public static void tearDownClass() { + try { + server.shutdown(); + } catch (final IOException e) { + e.printStackTrace(); + } + try { + thread.join(); + } catch (final InterruptedException e) { + // ignore + } + } + + public TcpJsonSocketServerTest() { + super(Protocol.TCP, PORT, false); + } + + @Override + protected Layout<String> createLayout() { + return super.createJsonLayout(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpXmlSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpXmlSocketServerTest.java new file mode 100644 index 0000000..6a0ad13 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/TcpXmlSocketServerTest.java @@ -0,0 +1,65 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.net.Protocol; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class TcpXmlSocketServerTest extends AbstractSocketServerTest { + + private static TcpSocketServer<InputStream> server; + + @BeforeClass + public static void setupClass() throws Exception { + LoggerContext.getContext(false).reconfigure(); + // Use a large buffer just to test the code, the UDP test uses a tiny buffer + server = new TcpSocketServer<>(PORT_NUM, new XmlInputStreamLogEventBridge(1024 * 100, + Charset.defaultCharset())); + thread = server.startNewThread(); + } + + @AfterClass + public static void tearDownClass() { + try { + server.shutdown(); + } catch (final IOException e) { + e.printStackTrace(); + } + try { + thread.join(); + } catch (final InterruptedException e) { + // ignore + } + } + + public TcpXmlSocketServerTest() { + super(Protocol.TCP, PORT, false); + } + + @Override + protected Layout<String> createLayout() { + return super.createXmlLayout(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadIdFilter.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadIdFilter.java new file mode 100644 index 0000000..d98e3f4 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadIdFilter.java @@ -0,0 +1,40 @@ +/* + * 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.server; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.filter.AbstractFilter; + +/** + * TODO Should use thread ID cache? + * @since 2.6 + */ +public class ThreadIdFilter extends AbstractFilter { + + private static final long serialVersionUID = 1L; + + public ThreadIdFilter(final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + } + + @Override + public Filter.Result filter(final LogEvent event) { + return event.getThreadId() == Thread.currentThread().getId() ? onMatch : onMismatch; + } +} \ No newline at end of file diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadNameFilter.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadNameFilter.java new file mode 100644 index 0000000..4204ac1 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadNameFilter.java @@ -0,0 +1,39 @@ +/* + * 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.server; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.filter.AbstractFilter; + +/** + * TODO Should use thread name cache? + */ +public class ThreadNameFilter extends AbstractFilter { + + private static final long serialVersionUID = 1L; + + public ThreadNameFilter(final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + } + + @Override + public Filter.Result filter(final LogEvent event) { + return event.getThreadName().equals(Thread.currentThread().getName()) ? onMatch : onMismatch; + } +} \ No newline at end of file diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadPriorityFilter.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadPriorityFilter.java new file mode 100644 index 0000000..6074f86 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/ThreadPriorityFilter.java @@ -0,0 +1,40 @@ +/* + * 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.server; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.filter.AbstractFilter; + +/** + * TODO Should use thread priority cache? + * @since 2.6 + */ +public class ThreadPriorityFilter extends AbstractFilter { + + private static final long serialVersionUID = 1L; + + public ThreadPriorityFilter(final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + } + + @Override + public Filter.Result filter(final LogEvent event) { + return event.getThreadPriority() == Thread.currentThread().getPriority() ? onMatch : onMismatch; + } +} \ No newline at end of file diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpJsonSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpJsonSocketServerTest.java new file mode 100644 index 0000000..e6b6d93 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpJsonSocketServerTest.java @@ -0,0 +1,58 @@ +/* + * 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.server; + +import java.io.InputStream; +import java.io.Serializable; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.net.Protocol; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class UdpJsonSocketServerTest extends AbstractSocketServerTest { + + private static UdpSocketServer<InputStream> server; + + @BeforeClass + public static void setupClass() throws Exception { + LoggerContext.getContext(false).reconfigure(); + server = UdpSocketServer.createJsonSocketServer(PORT_NUM); + thread = server.startNewThread(); + } + + @AfterClass + public static void tearDownClass() { + server.shutdown(); + try { + thread.join(); + } catch (final InterruptedException e) { + // ignore + } + } + + public UdpJsonSocketServerTest() { + super(Protocol.UDP, PORT, true); + } + + @Override + protected Layout<? extends Serializable> createLayout() { + return super.createJsonLayout(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpXmlSocketServerTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpXmlSocketServerTest.java new file mode 100644 index 0000000..9cbb07f --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/UdpXmlSocketServerTest.java @@ -0,0 +1,61 @@ +/* + * 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.server; + +import java.io.InputStream; +import java.io.Serializable; +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.net.Protocol; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class UdpXmlSocketServerTest extends AbstractSocketServerTest { + + private static UdpSocketServer<InputStream> server; + + @BeforeClass + public static void setupClass() throws Exception { + LoggerContext.getContext(false).reconfigure(); + // Use a tiny buffer just to test the code, the TCP test uses a large buffer + server = new UdpSocketServer<>(PORT_NUM, new XmlInputStreamLogEventBridge(100, + Charset.defaultCharset())); + thread = server.startNewThread(); + } + + @AfterClass + public static void tearDownClass() { + server.shutdown(); + try { + thread.join(); + } catch (final InterruptedException e) { + // ignore + } + } + + public UdpXmlSocketServerTest() { + super(Protocol.UDP, PORT, true); + } + + @Override + protected Layout<? extends Serializable> createLayout() { + return super.createXmlLayout(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceHelper.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceHelper.java new file mode 100644 index 0000000..e1fb522 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceHelper.java @@ -0,0 +1,51 @@ +/* + * 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.server.mom.activemq; + +import java.io.IOException; + +import org.apache.activemq.broker.BrokerService; + +/** + * Helps starts an embedded Apache ActiveMQ service broker. + */ +public class ActiveMqBrokerServiceHelper { + + public static BrokerService startBrokerService(final String brokerName, String brokerUrlString, final int port) + throws Exception { + // TODO Abstract out scheme + brokerUrlString = "tcp://localhost:" + port; + final BrokerService broker = new BrokerService(); + // configure the Broker + broker.setBrokerName(brokerName); + broker.addConnector(brokerUrlString); + broker.setPersistent(false); + broker.start(); + broker.waitUntilStarted(); + return broker; + } + + public static void stopBrokerService(final BrokerService brokerService) throws IOException, Exception { + if (brokerService != null) { + brokerService.deleteAllMessages(); + brokerService.stop(); + brokerService.waitUntilStopped(); + } + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceRule.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceRule.java new file mode 100644 index 0000000..a3116ac --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/activemq/ActiveMqBrokerServiceRule.java @@ -0,0 +1,94 @@ +/* + * 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.server.mom.activemq; + +import org.apache.activemq.broker.BrokerService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.TestMarkers; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * JUnit {@link TestRule} to manage an in-JVM Apache ActiveMQ broker with socket + * communications between clients and broker. + */ +public class ActiveMqBrokerServiceRule implements TestRule { + + static final Logger logger = LogManager.getLogger(ActiveMqBrokerServiceRule.class); + + /** + * Apache Active MQ uses this property name to lookup which port to use to + * connect to a broker. + */ + public static final String PORT_PROPERTY_NAME = "org.apache.activemq.AMQ_PORT"; + + private final String brokerName; + + private String brokerUrlString; + + private final String portPropertyName; + + public ActiveMqBrokerServiceRule(final String brokerName, final String portPropertyName) { + this.brokerName = brokerName; + this.portPropertyName = portPropertyName; + } + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + final BrokerService broker = ActiveMqBrokerServiceHelper.startBrokerService(brokerName, brokerUrlString, + Integer.parseInt(System.getProperty(portPropertyName))); + logger.debug(TestMarkers.TEST_RULE_LIFE_CYCLE, "{} started Apache Active MQ {}", + this.getClass().getSimpleName(), this); + try { + base.evaluate(); + } finally { + ActiveMqBrokerServiceHelper.stopBrokerService(broker); + logger.debug(TestMarkers.TEST_RULE_LIFE_CYCLE, "{} stopped Apache Active MQ {}", + this.getClass().getSimpleName(), this); + } + } + + }; + } + + public String getBrokerName() { + return brokerName; + } + + public String getBrokerUrlString() { + return brokerUrlString; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("ActiveMqBrokerServiceRule [brokerName="); + builder.append(brokerName); + builder.append(", bindAddress="); + builder.append(brokerUrlString); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiverTest.java b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiverTest.java new file mode 100644 index 0000000..ba6ea87 --- /dev/null +++ b/log4j-server/src/test/java/org/apache/logging/log4j/server/mom/jms/JmsQueueReceiverTest.java @@ -0,0 +1,42 @@ +/* + * 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.server.mom.jms; + +import org.apache.logging.log4j.server.mom.activemq.ActiveMqBrokerServiceRule; +import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule; +import org.apache.logging.log4j.test.RuleChainFactory; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class JmsQueueReceiverTest { + + private static final AvailablePortSystemPropertyTestRule portRule = AvailablePortSystemPropertyTestRule + .create(ActiveMqBrokerServiceRule.PORT_PROPERTY_NAME); + + private static final ActiveMqBrokerServiceRule activeMqBrokerServiceRule = new ActiveMqBrokerServiceRule( + JmsQueueReceiverTest.class.getName(), portRule.getName()); + + @ClassRule + public static RuleChain ruleChain = RuleChainFactory.create(portRule, activeMqBrokerServiceRule); + + @Test + public void testMain() throws Exception { + new JmsQueueReceiver().doMain(false, new String[] { "org.apache.activemq.jndi.ActiveMQInitialContextFactory", + "tcp://localhost:" + portRule.getPort(), "testq", "admin", "admin", "queue.testq", "testq" }); + } +} diff --git a/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks b/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks new file mode 100644 index 0000000..36f11b6 Binary files /dev/null and b/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks differ diff --git a/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks b/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks new file mode 100644 index 0000000..0e6aaf2 Binary files /dev/null and b/log4j-server/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks differ
