Skip to content

Commit

Permalink
Add a Jakarta JMS Appender #2295 (#3247)
Browse files Browse the repository at this point in the history
  • Loading branch information
garydgregory authored Dec 7, 2024
1 parent df91908 commit 43a0e29
Show file tree
Hide file tree
Showing 15 changed files with 1,127 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ updates:
- "/log4j-docker"
- "/log4j-fuzz-test"
- "/log4j-iostreams"
- "/log4j-jakarta-jms"
- "/log4j-jakarta-smtp"
- "/log4j-jakarta-web"
- "/log4j-jcl"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@
import org.apache.logging.log4j.core.net.JndiManager;

/**
* Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However,
* configurations set up for the 2.0 version of the JMS appenders will still work.
* Javax JMS Appender plugin. This Appender replaces the previous split classes.
* Configurations set up for the 2.0 version of the JMS appenders will still work.
*
* @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsAppender}.
*/
@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
@PluginAliases({"JMSQueue", "JMSTopic"})
@Deprecated
@Plugin(name = "JMS-Javax", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
@PluginAliases({"JMS", "JMSQueue", "JMSTopic"})
public class JmsAppender extends AbstractAppender {

public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@
* Consider this class <b>private</b>; it is only <b>public</b> for access by integration tests.
*
* <p>
* JMS connection and session manager. Can be used to access MessageProducer, MessageConsumer, and Message objects
* involving a configured ConnectionFactory and Destination.
* JMS connection and destination manager. Uses a MessageProducer to send log events to a JMS Destination.
* </p>
*
* @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsManager}.
*/
@Deprecated
public class JmsManager extends AbstractManager {

public static class JmsManagerConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* @since 2.1
*/
@Export
@Version("2.20.1")
@Version("2.25.0")
package org.apache.logging.log4j.core.appender.mom;

import org.osgi.annotation.bundle.Export;
Expand Down
1 change: 1 addition & 0 deletions log4j-jakarta-jms/.log4j-plugin-processing-activator
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file is here to activate the `plugin-processing` Maven profile.
71 changes: 71 additions & 0 deletions log4j-jakarta-jms/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${revision}</version>
<relativePath>../log4j-parent</relativePath>
</parent>
<artifactId>log4j-jakarta-jms</artifactId>
<name>Apache Log4j Jakarta JMS</name>
<description>Apache Log4j Java Message Service (JMS), version for Jakarta.</description>
<properties>
<!-- This is a new module in 2.25.0, remove this property after the release. -->
<bnd.baseline.skip>true</bnd.baseline.skip>
<!-- OSGi and JPMS options -->
<bnd-module-name>org.apache.logging.log4j.jakarta.jms</bnd-module-name>
<Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.jms</groupId>
<artifactId>jakarta.jms-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.core.appender.mom.jakarta;

import jakarta.jms.JMSException;
import java.io.Serializable;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
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.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AbstractManager;
import org.apache.logging.log4j.core.appender.mom.jakarta.JmsManager.JmsManagerConfiguration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.net.JndiManager;

/**
* Jakarta JMS Appender plugin for both queues and topics.
*/
@Plugin(name = "JMS-Jakarta", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
public final class JmsAppender extends AbstractAppender {

public static final class Builder extends AbstractAppender.Builder<Builder>
implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {

public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;

@PluginBuilderAttribute
private String factoryName;

@PluginBuilderAttribute
private String providerUrl;

@PluginBuilderAttribute
private String urlPkgPrefixes;

@PluginBuilderAttribute
private String securityPrincipalName;

@PluginBuilderAttribute(sensitive = true)
private String securityCredentials;

@PluginBuilderAttribute
@Required(message = "A jakarta.jms.ConnectionFactory JNDI name must be specified")
private String factoryBindingName;

@PluginBuilderAttribute
@PluginAliases({"queueBindingName", "topicBindingName"})
@Required(message = "A jakarta.jms.Destination JNDI name must be specified")
private String destinationBindingName;

@PluginBuilderAttribute
private String userName;

@PluginBuilderAttribute(sensitive = true)
private char[] password;

@PluginBuilderAttribute
private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS;

@PluginBuilderAttribute
private boolean immediateFail;

// Programmatic access only for now.
private JmsManager jmsManager;

private Builder() {}

@SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
@Override
public JmsAppender build() {
JmsManager actualJmsManager = jmsManager;
JmsManagerConfiguration configuration = null;
if (actualJmsManager == null) {
final Properties jndiProperties = JndiManager.createProperties(
factoryName, providerUrl, urlPkgPrefixes, securityPrincipalName, securityCredentials, null);
configuration = new JmsManagerConfiguration(
jndiProperties,
factoryBindingName,
destinationBindingName,
userName,
password,
immediateFail,
reconnectIntervalMillis);
actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration);
}
if (actualJmsManager == null) {
// JmsManagerFactory has already logged an ERROR.
return null;
}
final Layout<? extends Serializable> layout = getLayout();
if (layout == null) {
LOGGER.error("No layout provided for JmsAppender");
return null;
}
try {
return new JmsAppender(
getName(), getFilter(), layout, isIgnoreExceptions(), getPropertyArray(), actualJmsManager);
} catch (final JMSException e) {
// Never happens since the ctor no longer actually throws a JMSException.
throw new IllegalStateException(e);
}
}

public Builder setDestinationBindingName(final String destinationBindingName) {
this.destinationBindingName = destinationBindingName;
return this;
}

public Builder setFactoryBindingName(final String factoryBindingName) {
this.factoryBindingName = factoryBindingName;
return this;
}

public Builder setFactoryName(final String factoryName) {
this.factoryName = factoryName;
return this;
}

public Builder setImmediateFail(final boolean immediateFail) {
this.immediateFail = immediateFail;
return this;
}

public Builder setJmsManager(final JmsManager jmsManager) {
this.jmsManager = jmsManager;
return this;
}

public Builder setPassword(final char[] password) {
this.password = password;
return this;
}

public Builder setProviderUrl(final String providerUrl) {
this.providerUrl = providerUrl;
return this;
}

public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) {
this.reconnectIntervalMillis = reconnectIntervalMillis;
return this;
}

public Builder setSecurityCredentials(final String securityCredentials) {
this.securityCredentials = securityCredentials;
return this;
}

public Builder setSecurityPrincipalName(final String securityPrincipalName) {
this.securityPrincipalName = securityPrincipalName;
return this;
}

public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
this.urlPkgPrefixes = urlPkgPrefixes;
return this;
}

public Builder setUserName(final String userName) {
this.userName = userName;
return this;
}

/**
* Does not include the password.
*/
@Override
public String toString() {
return "Builder [name=" + getName() + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl
+ ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName
+ ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName
+ ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout="
+ getLayout() + ", filter=" + getFilter() + ", ignoreExceptions=" + isIgnoreExceptions()
+ ", jmsManager=" + jmsManager + "]";
}
}

@PluginBuilderFactory
public static Builder newBuilder() {
return new Builder();
}

private volatile JmsManager manager;

/**
* Constructs a new instance.
*
* @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
*/
private JmsAppender(
final String name,
final Filter filter,
final Layout<? extends Serializable> layout,
final boolean ignoreExceptions,
final Property[] properties,
final JmsManager manager)
throws JMSException {
super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
}

@Override
public void append(final LogEvent event) {
this.manager.send(event, toSerializable(event));
}

public JmsManager getManager() {
return manager;
}

@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
setStopping();
boolean stopped = super.stop(timeout, timeUnit, false);
stopped &= this.manager.stop(timeout, timeUnit);
setStopped();
return stopped;
}
}
Loading

0 comments on commit 43a0e29

Please sign in to comment.