diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index 49293f47dfc..405d8914834 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -59,6 +59,7 @@
com.conversantmedia.util.concurrent;resolution:=optional;
com.fasterxml.jackson.*;resolution:=optional,
+ tools.jackson.databind.*;resolution:=optional,
com.lmax.disruptor.*;version="${disruptor.support.range}";resolution:=optional,
javax.activation;resolution:=optional,
javax.jms;version="[1.1,3)";resolution:=optional,
@@ -193,6 +194,11 @@
jackson-dataformat-yaml
true
+
+ tools.jackson.core
+ jackson-databind
+ true
+
org.jctools
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3Configuration.java
new file mode 100644
index 00000000000..990e9db309f
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3Configuration.java
@@ -0,0 +1,215 @@
+package io.github.ashr123.logging;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.*;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.config.status.StatusConfiguration;
+import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.core.util.Patterns;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class Json3Configuration extends AbstractConfiguration implements Reconfigurable {
+
+ private final List status = new ArrayList<>();
+ private JsonNode root = null;
+
+ public Json3Configuration(final LoggerContext loggerContext, final ConfigurationSource configSource) {
+ super(loggerContext, configSource);
+// final File configFile = configSource.getFile();
+ byte[] buffer;
+ try {
+ try (final InputStream configStream = configSource.getInputStream()) {
+ buffer = toByteArray(configStream);
+ }
+ final InputStream is = new ByteArrayInputStream(buffer);
+ root = new JsonMapper().readTree(is);
+ if (root.size() == 1) {
+ for (final JsonNode node : root) {
+ root = node;
+ }
+ }
+ processAttributes(rootNode, root);
+ final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(getDefaultStatus());
+ int monitorIntervalSeconds = 0;
+ for (final Map.Entry entry :
+ rootNode.getAttributes().entrySet()) {
+ final String key = entry.getKey();
+ final String value = getConfigurationStrSubstitutor().replace(entry.getValue());
+ // TODO: this duplicates a lot of the XmlConfiguration constructor
+ if ("status".equalsIgnoreCase(key)) {
+ statusConfig.withStatus(value);
+ } else if ("dest".equalsIgnoreCase(key)) {
+ statusConfig.withDestination(value);
+ } else if ("shutdownHook".equalsIgnoreCase(key)) {
+ isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
+ } else if ("shutdownTimeout".equalsIgnoreCase(key)) {
+ shutdownTimeoutMillis = Long.parseLong(value);
+ } else if ("packages".equalsIgnoreCase(key)) {
+ pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
+ } else if ("name".equalsIgnoreCase(key)) {
+ setName(value);
+ } else if ("monitorInterval".equalsIgnoreCase(key)) {
+ monitorIntervalSeconds = Integers.parseInt(value);
+ } else if ("advertiser".equalsIgnoreCase(key)) {
+ createAdvertiser(value, configSource, buffer, "application/json");
+ }
+ }
+ initializeWatchers(this, configSource, monitorIntervalSeconds);
+ statusConfig.initialize();
+ if (getName() == null) {
+ setName(configSource.getLocation());
+ }
+ } catch (final Exception ex) {
+ LOGGER.error("Error parsing {}", configSource.getLocation(), ex);
+ }
+ }
+
+ @Override
+ public void setup() {
+ final List children = rootNode.getChildren();
+ root.propertyStream()
+ .forEach(entry -> {
+ if (entry.getValue().isObject()) {
+ LOGGER.debug("Processing node for object {}", entry.getKey());
+ children.add(constructNode(entry.getKey(), rootNode, entry.getValue()));
+ } else if (entry.getValue().isArray()) {
+ LOGGER.error("Arrays are not supported at the root configuration.");
+ }
+ });
+ LOGGER.debug("Completed parsing configuration");
+ if (!status.isEmpty()) {
+ for (final Json3Configuration.Status s : status) {
+ LOGGER.error("Error processing element {}: {}", s.name(), s.errorType());
+ }
+ }
+ }
+
+ @Override
+ public Configuration reconfigure() {
+ try {
+ final ConfigurationSource source = getConfigurationSource().resetInputStream();
+ if (source == null) {
+ return null;
+ }
+ return new Json3Configuration(getLoggerContext(), source);
+ } catch (final IOException ex) {
+ LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
+ }
+ return null;
+ }
+
+ private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
+ final PluginType> type = pluginManager.getPluginType(name);
+ final Node node = new Node(parent, name, type);
+ processAttributes(node, jsonNode);
+ final List children = node.getChildren();
+ jsonNode.propertyStream()
+ .forEach(entry -> {
+ final JsonNode n = entry.getValue();
+ if (n.isArray() || n.isObject()) {
+ if (type == null) {
+ status.add(new Json3Configuration.Status(name, n, Json3Configuration.ErrorType.CLASS_NOT_FOUND));
+ }
+ if (n.isArray()) {
+ LOGGER.debug("Processing node for array {}", entry.getKey());
+ for (int i = 0; i < n.size(); ++i) {
+ final String pluginType = getType(n.get(i), entry.getKey());
+ final Node item = new Node(
+ node,
+ entry.getKey(),
+ pluginManager.getPluginType(pluginType)
+ );
+ processAttributes(item, n.get(i));
+ if (pluginType.equals(entry.getKey())) {
+ LOGGER.debug("Processing {}[{}]", entry.getKey(), i);
+ } else {
+ LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i);
+ }
+ final List itemChildren = item.getChildren();
+ n.get(i)
+ .propertyStream()
+ .forEach(itemEntry -> {
+ if (itemEntry.getValue().isObject()) {
+ LOGGER.debug("Processing node for object {}", itemEntry.getKey());
+ itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
+ } else if (itemEntry.getValue().isArray()) {
+ final JsonNode array = itemEntry.getValue();
+ final String entryName = itemEntry.getKey();
+ LOGGER.debug("Processing array for object {}", entryName);
+ for (int j = 0; j < array.size(); ++j) {
+ itemChildren.add(constructNode(entryName, item, array.get(j)));
+ }
+ }
+ });
+
+ children.add(item);
+ }
+ } else {
+ LOGGER.debug("Processing node for object {}", entry.getKey());
+ children.add(constructNode(entry.getKey(), node, n));
+ }
+ } else {
+ LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
+ }
+ });
+
+ LOGGER.debug(
+ "Returning {} with parent {} of type {}",
+ node.getName(),
+ node.getParent() == null
+ ? "null"
+ : node.getParent().getName() == null
+ ? LoggerConfig.ROOT
+ : node.getParent().getName(),
+ type == null
+ ? "null"
+ : type.getElementName() + ':' + type.getPluginClass()
+ );
+ return node;
+ }
+
+ private static String getType(final JsonNode node, final String name) {
+ return node.propertyStream()
+ .filter(entry -> "type".equalsIgnoreCase(entry.getKey()) &&
+ entry.getValue().isValueNode())
+ .findFirst()
+ .map(Map.Entry::getValue)
+ .map(JsonNode::asString)
+ .orElse(name);
+ }
+
+ private static void processAttributes(final Node parent, final JsonNode node) {
+ final Map attrs = parent.getAttributes();
+ node.propertyStream()
+ .filter(entry -> !"type".equalsIgnoreCase(entry.getKey()) &&
+ entry.getValue().isValueNode())
+ .forEach(entry -> attrs.put(entry.getKey(), entry.getValue().asString()));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
+ }
+
+ /**
+ * The error that occurred.
+ */
+ private enum ErrorType {
+ CLASS_NOT_FOUND
+ }
+
+ /**
+ * Status for recording errors.
+ */
+ private record Status(String name, JsonNode node, ErrorType errorType) {
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3ConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3ConfigurationFactory.java
new file mode 100644
index 00000000000..df4bb6e8fa3
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/Json3ConfigurationFactory.java
@@ -0,0 +1,57 @@
+package io.github.ashr123.logging;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Order;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.util.Loader;
+
+@Plugin(name = "Json3ConfigurationFactory", category = ConfigurationFactory.CATEGORY)
+@Order(50)
+public class Json3ConfigurationFactory extends ConfigurationFactory {
+
+ /**
+ * The file extensions supported by this factory.
+ */
+ private static final String[] SUFFIXES = {".json", ".jsn"};
+
+ private static final String[] dependencies = {
+ "tools.jackson.databind.json.JsonMapper",
+ "tools.jackson.databind.JsonNode"
+ };
+
+ private final boolean isActive;
+
+ public Json3ConfigurationFactory() {
+ for (final String dependency : dependencies) {
+ if (!Loader.isClassAvailable(dependency)) {
+ LOGGER.debug(
+ "Missing dependencies for Json support, ConfigurationFactory {} is inactive",
+ getClass().getName()
+ );
+ isActive = false;
+ return;
+ }
+ }
+ isActive = true;
+ }
+
+ @Override
+ protected boolean isActive() {
+ return isActive;
+ }
+
+ @Override
+ public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
+ return isActive
+ ? new Json3Configuration(loggerContext, source)
+ : null;
+ }
+
+ @Override
+ public String[] getSupportedTypes() {
+ return SUFFIXES;
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java
index 9d9e5d60eb3..0eab26e30a4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java
@@ -18,7 +18,7 @@
* Classes and interfaces supporting configuration of Log4j 2 with JSON.
*/
@Export
-@Version("2.20.1")
+@Version("2.26.0")
package org.apache.logging.log4j.core.config.json;
import org.osgi.annotation.bundle.Export;
diff --git a/src/changelog/.2.x.x/adding_support_for_jackson_databind_3.xml b/src/changelog/.2.x.x/adding_support_for_jackson_databind_3.xml
new file mode 100644
index 00000000000..0e21ff49440
--- /dev/null
+++ b/src/changelog/.2.x.x/adding_support_for_jackson_databind_3.xml
@@ -0,0 +1,8 @@
+
+
+
+ Adding support for jackson databind 3
+