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 +