You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2021/03/22 20:11:51 UTC
[logging-log4j2] 01/01: LOG4J2-3004 Add plugin support to
JsonTemplateLayout.
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch LOG4J2-3004
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 74d0b56f5542895c9f665eb3cbcad1cd3cad80b5
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Mon Mar 22 20:57:48 2021 +0100
LOG4J2-3004 Add plugin support to JsonTemplateLayout.
---
.../plugins/convert/TypeConverterRegistry.java | 59 +-
.../log4j/core/config/plugins/util/PluginUtil.java | 96 ++++
.../plugins/convert/TypeConverterRegistryTest.java | 83 ++-
log4j-layout-template-json/revapi.json | 279 ++++++++++
.../layout/template/json/JsonTemplateLayout.java | 60 +-
.../template/json/resolver/EndOfBatchResolver.java | 5 +-
.../json/resolver/EndOfBatchResolverFactory.java | 15 +-
.../resolver/EventAdditionalFieldInterceptor.java | 98 ++++
.../template/json/resolver/EventResolver.java | 5 +-
.../json/resolver/EventResolverContext.java | 110 ++--
.../json/resolver/EventResolverFactories.java | 49 +-
.../json/resolver/EventResolverFactory.java | 21 +-
...Resolver.java => EventResolverInterceptor.java} | 18 +-
...esolver.java => EventResolverInterceptors.java} | 19 +-
...er.java => EventResolverStringSubstitutor.java} | 38 +-
.../resolver/EventRootObjectKeyInterceptor.java | 53 ++
.../template/json/resolver/ExceptionResolver.java | 151 ++++-
.../json/resolver/ExceptionResolverFactory.java | 13 +-
.../json/resolver/ExceptionRootCauseResolver.java | 8 +-
.../ExceptionRootCauseResolverFactory.java | 15 +-
.../template/json/resolver/LevelResolver.java | 29 +-
.../json/resolver/LevelResolverFactory.java | 12 +-
.../template/json/resolver/LoggerResolver.java | 9 +-
.../json/resolver/LoggerResolverFactory.java | 12 +-
.../template/json/resolver/MainMapResolver.java | 2 +-
.../json/resolver/MainMapResolverFactory.java | 12 +-
.../layout/template/json/resolver/MapResolver.java | 2 +-
.../template/json/resolver/MapResolverFactory.java | 12 +-
.../template/json/resolver/MarkerResolver.java | 2 +-
.../json/resolver/MarkerResolverFactory.java | 16 +-
.../json/resolver/MessageParameterResolver.java | 2 +-
.../resolver/MessageParameterResolverFactory.java | 15 +-
.../template/json/resolver/MessageResolver.java | 2 +-
.../json/resolver/MessageResolverFactory.java | 12 +-
.../template/json/resolver/PatternResolver.java | 2 +-
.../json/resolver/PatternResolverFactory.java | 12 +-
.../template/json/resolver/SourceResolver.java | 22 +-
.../json/resolver/SourceResolverFactory.java | 12 +-
.../StackTraceElementObjectResolverContext.java | 93 ----
.../StackTraceElementObjectResolverFactories.java | 41 --
...esolver.java => StackTraceElementResolver.java} | 27 +-
.../resolver/StackTraceElementResolverContext.java | 121 ++++
....java => StackTraceElementResolverFactory.java} | 33 +-
...tackTraceElementResolverStringSubstitutor.java} | 39 +-
.../json/resolver/StackTraceObjectResolver.java | 3 +
.../template/json/resolver/StackTraceResolver.java | 3 +
.../json/resolver/StackTraceStringResolver.java | 3 +
.../json/resolver/TemplateResolverConfig.java | 37 +-
.../json/resolver/TemplateResolverContext.java | 32 +-
.../json/resolver/TemplateResolverFactories.java | 130 +++++
.../json/resolver/TemplateResolverFactory.java | 25 +-
.../json/resolver/TemplateResolverInterceptor.java | 56 ++
.../resolver/TemplateResolverInterceptors.java | 131 +++++
...java => TemplateResolverStringSubstitutor.java} | 22 +-
.../template/json/resolver/TemplateResolvers.java | 109 +---
.../json/resolver/ThreadContextDataResolver.java | 2 +-
.../resolver/ThreadContextDataResolverFactory.java | 13 +-
.../json/resolver/ThreadContextStackResolver.java | 2 +-
.../ThreadContextStackResolverFactory.java | 17 +-
.../template/json/resolver/ThreadResolver.java | 12 +-
.../json/resolver/ThreadResolverFactory.java | 12 +-
.../template/json/resolver/TimestampResolver.java | 4 +-
.../json/resolver/TimestampResolverFactory.java | 12 +-
.../template/json/util/RecyclerFactories.java | 11 -
.../RecyclerFactoryConverter.java} | 29 +-
.../src/main/resources/EcsLayout.json | 1 +
.../log4j/layout/template/json/EcsLayoutTest.java | 10 +
.../template/json/JsonTemplateLayoutTest.java | 124 ++++-
.../log4j/layout/template/json/LogstashIT.java | 3 +-
.../json/JsonTemplateLayoutBenchmarkState.java | 11 +-
pom.xml | 2 +-
src/changes/changes.xml | 5 +
...layout.vm.adoc => json-template-layout.adoc.vm} | 616 ++++++++++++++++-----
src/site/xdoc/manual/layouts.xml.vm | 7 +-
src/site/xdoc/manual/plugins.xml | 3 +
75 files changed, 2499 insertions(+), 684 deletions(-)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java
index 5088f15..3964370 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java
@@ -86,8 +86,9 @@ public class TypeConverterRegistry {
if (clazz.isEnum()) {
@SuppressWarnings({"unchecked","rawtypes"})
final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
- registry.putIfAbsent(type, converter);
- return converter;
+ synchronized (INSTANCE_LOCK) {
+ return registerConverter(type, converter);
+ }
}
}
// look for compatible converters
@@ -96,8 +97,9 @@ public class TypeConverterRegistry {
if (TypeUtil.isAssignable(type, key)) {
LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
final TypeConverter<?> value = entry.getValue();
- registry.putIfAbsent(type, value);
- return value;
+ synchronized (INSTANCE_LOCK) {
+ return registerConverter(type, value);
+ }
}
}
throw new UnknownFormatConversionException(type.toString());
@@ -119,11 +121,52 @@ public class TypeConverterRegistry {
final Class<? extends TypeConverter> pluginClass = clazz.asSubclass(TypeConverter.class);
final Type conversionType = getTypeConverterSupportedType(pluginClass);
final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
- if (registry.putIfAbsent(conversionType, converter) != null) {
- LOGGER.warn("Found a TypeConverter [{}] for type [{}] that already exists.", converter,
- conversionType);
- }
+ registerConverter(conversionType, converter);
+ }
+ }
+ }
+
+ /**
+ * Attempts to register the given converter and returns the effective
+ * converter associated with the given type.
+ * <p>
+ * Registration will fail if there already exists a converter for the given
+ * type and neither the existing, nor the provided converter extends from {@link Comparable}.
+ */
+ private TypeConverter<?> registerConverter(
+ final Type conversionType,
+ final TypeConverter<?> converter) {
+ final TypeConverter<?> conflictingConverter = registry.get(conversionType);
+ if (conflictingConverter != null) {
+ final boolean overridable;
+ if (converter instanceof Comparable) {
+ @SuppressWarnings("unchecked")
+ final Comparable<TypeConverter<?>> comparableConverter =
+ (Comparable<TypeConverter<?>>) converter;
+ overridable = comparableConverter.compareTo(conflictingConverter) < 0;
+ } else if (conflictingConverter instanceof Comparable) {
+ @SuppressWarnings("unchecked")
+ final Comparable<TypeConverter<?>> comparableConflictingConverter =
+ (Comparable<TypeConverter<?>>) conflictingConverter;
+ overridable = comparableConflictingConverter.compareTo(converter) > 0;
+ } else {
+ overridable = false;
+ }
+ if (overridable) {
+ LOGGER.debug(
+ "Replacing TypeConverter [{}] for type [{}] with [{}] after comparison.",
+ conflictingConverter, conversionType, converter);
+ registry.put(conversionType, converter);
+ return converter;
+ } else {
+ LOGGER.warn(
+ "Ignoring TypeConverter [{}] for type [{}] that conflicts with [{}], since they are not comparable.",
+ converter, conversionType, conflictingConverter);
+ return conflictingConverter;
}
+ } else {
+ registry.put(conversionType, converter);
+ return converter;
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginUtil.java
new file mode 100644
index 0000000..0f1c92b
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginUtil.java
@@ -0,0 +1,96 @@
+/*
+ * 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.config.plugins.util;
+
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * {@link org.apache.logging.log4j.core.config.plugins.Plugin} utilities.
+ *
+ * @see PluginManager
+ */
+public final class PluginUtil {
+
+ private PluginUtil() {}
+
+ /**
+ * Shortcut for collecting plugins matching with the given {@code category}.
+ */
+ public static Map<String, PluginType<?>> collectPluginsByCategory(final String category) {
+ Objects.requireNonNull(category, "category");
+ return collectPluginsByCategoryAndPackage(category, Collections.emptyList());
+ }
+
+ /**
+ * Short for collecting plugins matching with the given {@code category} in provided {@code packages}.
+ */
+ public static Map<String, PluginType<?>> collectPluginsByCategoryAndPackage(
+ final String category,
+ final List<String> packages) {
+ Objects.requireNonNull(category, "category");
+ Objects.requireNonNull(packages, "packages");
+ final PluginManager pluginManager = new PluginManager(category);
+ pluginManager.collectPlugins(packages);
+ return pluginManager.getPlugins();
+ }
+
+ /**
+ * Instantiates the given plugin using its no-arg {@link PluginFactory}-annotated static method.
+ * @throws IllegalStateException if instantiation fails
+ */
+ public static <V> V instantiatePlugin(Class<V> pluginClass) {
+ Objects.requireNonNull(pluginClass, "pluginClass");
+ final Method pluginFactoryMethod = findPluginFactoryMethod(pluginClass);
+ try {
+ @SuppressWarnings("unchecked")
+ final V instance = (V) pluginFactoryMethod.invoke(null);
+ return instance;
+ } catch (IllegalAccessException | InvocationTargetException error) {
+ final String message = String.format(
+ "failed to instantiate plugin of type %s using the factory method %s",
+ pluginClass, pluginFactoryMethod);
+ throw new IllegalStateException(message, error);
+ }
+ }
+
+ /**
+ * Finds the {@link PluginFactory}-annotated static method of the given class.
+ * @throws IllegalStateException if no such method could be found
+ */
+ public static Method findPluginFactoryMethod(final Class<?> pluginClass) {
+ Objects.requireNonNull(pluginClass, "pluginClass");
+ for (final Method method : pluginClass.getDeclaredMethods()) {
+ final boolean methodAnnotated = method.isAnnotationPresent(PluginFactory.class);
+ if (methodAnnotated) {
+ final boolean methodStatic = Modifier.isStatic(method.getModifiers());
+ if (methodStatic) {
+ return method;
+ }
+ }
+ }
+ throw new IllegalStateException("no factory method found for class " + pluginClass);
+ }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java
index f9e757d..e00ce11 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.logging.log4j.core.config.plugins.convert;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.junit.Test;
import static org.hamcrest.Matchers.instanceOf;
@@ -24,7 +25,7 @@ import static org.junit.Assert.*;
public class TypeConverterRegistryTest {
@Test(expected = NullPointerException.class)
- public void testFindNullConverter() throws Exception {
+ public void testFindNullConverter() {
TypeConverterRegistry.getInstance().findCompatibleConverter(null);
}
@@ -63,7 +64,7 @@ public class TypeConverterRegistryTest {
// TODO: is there a specific converter this should return?
}
- public static enum Foo {
+ public enum Foo {
I, PITY, THE
}
@@ -77,4 +78,82 @@ public class TypeConverterRegistryTest {
assertEquals(Foo.PITY, fooTypeConverter.convert("pity"));
assertEquals(Foo.THE, fooTypeConverter.convert("THE"));
}
+
+ public static final class CustomTestClass1 {
+
+ private CustomTestClass1() {}
+
+ }
+
+ @Plugin(name = "CustomTestClass1Converter1", category = TypeConverters.CATEGORY)
+ public static final class CustomTestClass1Converter1
+ implements TypeConverter<CustomTestClass1> {
+
+ @Override
+ public CustomTestClass1 convert(final String ignored) {
+ return new CustomTestClass1();
+ }
+
+ }
+
+ @Plugin(name = "CustomTestClass1Converter2", category = TypeConverters.CATEGORY)
+ public static final class CustomTestClass1Converter2
+ implements TypeConverter<CustomTestClass1>, Comparable<Object> {
+
+ @Override
+ public CustomTestClass1 convert(final String ignored) {
+ return new CustomTestClass1();
+ }
+
+ @Override
+ public int compareTo(@SuppressWarnings("NullableProblems") final Object converter) {
+ return -1;
+ }
+
+ }
+
+ @Test
+ public void testMultipleComparableConverters() {
+ final TypeConverter<?> converter = TypeConverterRegistry
+ .getInstance()
+ .findCompatibleConverter(CustomTestClass1.class);
+ assertThat(converter, instanceOf(CustomTestClass1Converter2.class));
+ }
+
+ public static final class CustomTestClass2 {
+
+ private CustomTestClass2() {}
+
+ }
+
+ @Plugin(name = "CustomTestClass2Converter1", category = TypeConverters.CATEGORY)
+ public static final class CustomTestClass2Converter1
+ implements TypeConverter<CustomTestClass2> {
+
+ @Override
+ public CustomTestClass2 convert(final String ignored) {
+ return new CustomTestClass2();
+ }
+
+ }
+
+ @Plugin(name = "CustomTestClass2Converter2", category = TypeConverters.CATEGORY)
+ public static final class CustomTestClass2Converter2
+ implements TypeConverter<CustomTestClass2> {
+
+ @Override
+ public CustomTestClass2 convert(final String ignored) {
+ return new CustomTestClass2();
+ }
+
+ }
+
+ @Test
+ public void testMultipleIncomparableConverters() {
+ final TypeConverter<?> converter = TypeConverterRegistry
+ .getInstance()
+ .findCompatibleConverter(CustomTestClass2.class);
+ assertThat(converter, instanceOf(CustomTestClass2Converter1.class));
+ }
+
}
diff --git a/log4j-layout-template-json/revapi.json b/log4j-layout-template-json/revapi.json
index 6f6096d..62e4e7d 100644
--- a/log4j-layout-template-json/revapi.json
+++ b/log4j-layout-template-json/revapi.json
@@ -449,6 +449,285 @@
"code": "java.method.removed",
"old": "method org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField.Type org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField::getType()",
"justification": "LOG4J2-2973 Rename EventTemplateAdditionalField#type (conflicting with properties file parser) to #format."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.EndOfBatchResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"EndOfBatchResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method java.lang.String org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder::getTruncatedStringSuffix()",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder::setStackTraceElementObjectResolver(org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver<java.lang.StackTraceElement>)",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.method.parameterTypeChanged",
+ "old": "parameter org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder::setSubstitutor(===org.apache.logging.log4j.core.lookup.StrSubstitutor===)",
+ "new": "parameter org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext.Builder::setSubstitutor(===org.apache.logging.log4j.layout.template.json.resolver.EventResolverStringSubstitutor===)",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.method.returnTypeTypeParametersChanged",
+ "old": "method java.util.Map<java.lang.String, org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory<org.apache.logging.log4j.core.LogEvent, org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext, ? extends org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver<org.apache.logging.log4j.core.LogEvent>>> org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext::getResolverFactoryByName()",
+ "new": "method java.util.Map<java.lang.String, org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory> org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext::getResolverFactoryByName()",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.method.returnTypeChanged",
+ "old": "method org.apache.logging.log4j.core.lookup.StrSubstitutor org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext::getSubstitutor()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.EventResolverStringSubstitutor org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext::getSubstitutor()",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ExceptionResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ExceptionResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ExceptionRootCauseResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ExceptionRootCauseResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.LevelResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"LevelResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.LoggerResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"LoggerResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.MainMapResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"MainMapResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.MapResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"MapResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.MarkerResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"MarkerResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.MessageParameterResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"MessageParameterResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.MessageResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"MessageResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.PatternResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"PatternResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.SourceResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"SourceResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.StackTraceElementObjectResolverContext",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextDataResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ThreadContextDataResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ThreadContextDataResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ThreadResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "method org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory::getInstance()",
+ "new": "method org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory::getInstance()",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.TimestampResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"TimestampResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.added",
+ "old": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory",
+ "new": "class org.apache.logging.log4j.layout.template.json.resolver.ThreadContextStackResolverFactory",
+ "annotation": "@org.apache.logging.log4j.core.config.plugins.Plugin(name = \"ThreadContextStackResolverFactory\", category = \"JsonTemplateResolverFactory\")",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.annotation.attributeValueChanged",
+ "old": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories.RecyclerFactoryConverter",
+ "new": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories.RecyclerFactoryConverter",
+ "annotationType": "org.apache.logging.log4j.core.config.plugins.Plugin",
+ "attribute": "name",
+ "oldValue": "\"RecyclerFactory\"",
+ "newValue": "\"RecyclerFactoryConverter\"",
+ "justification": "LOG4J2-3004 Add plugin support."
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories.RecyclerFactoryConverter",
+ "justification": "LOG4J2-3004 Add plugin support."
}
]
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index b0a2b66..9276fd3 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -29,11 +29,14 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.Encoder;
import org.apache.logging.log4j.core.layout.LockingStringBuilderEncoder;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.StringEncoder;
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
-import org.apache.logging.log4j.layout.template.json.resolver.StackTraceElementObjectResolverContext;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactories;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptors;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverStringSubstitutor;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
@@ -44,6 +47,7 @@ import org.apache.logging.log4j.util.Strings;
import java.nio.charset.Charset;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
@@ -93,59 +97,56 @@ public class JsonTemplateLayout implements StringLayout {
final String eventDelimiterSuffix = builder.isNullEventDelimiterEnabled() ? "\0" : "";
this.eventDelimiter = builder.eventDelimiter + eventDelimiterSuffix;
final Configuration configuration = builder.configuration;
- final StrSubstitutor substitutor = configuration.getStrSubstitutor();
final JsonWriter jsonWriter = JsonWriter
.newBuilder()
.setMaxStringLength(builder.maxStringLength)
.setTruncatedStringSuffix(builder.truncatedStringSuffix)
.build();
- final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver =
- builder.stackTraceEnabled
- ? createStackTraceElementResolver(builder, substitutor, jsonWriter)
- : null;
this.eventResolver = createEventResolver(
builder,
configuration,
- substitutor,
charset,
- jsonWriter,
- stackTraceElementObjectResolver);
+ jsonWriter);
this.contextRecycler = createContextRecycler(builder, jsonWriter);
}
- private static TemplateResolver<StackTraceElement> createStackTraceElementResolver(
- final Builder builder,
- final StrSubstitutor substitutor,
- final JsonWriter jsonWriter) {
- final StackTraceElementObjectResolverContext stackTraceElementObjectResolverContext =
- StackTraceElementObjectResolverContext
- .newBuilder()
- .setSubstitutor(substitutor)
- .setJsonWriter(jsonWriter)
- .build();
- final String stackTraceElementTemplate = readStackTraceElementTemplate(builder);
- return TemplateResolvers.ofTemplate(stackTraceElementObjectResolverContext, stackTraceElementTemplate);
- }
-
private TemplateResolver<LogEvent> createEventResolver(
final Builder builder,
final Configuration configuration,
- final StrSubstitutor substitutor,
final Charset charset,
- final JsonWriter jsonWriter,
- final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver) {
+ final JsonWriter jsonWriter) {
+
+ // Inject resolver factory and interceptor plugins.
+ final List<String> pluginPackages = configuration.getPluginPackages();
+ final Map<String, EventResolverFactory> resolverFactoryByName =
+ EventResolverFactories.populateResolverFactoryByName(pluginPackages);
+ final List<EventResolverInterceptor> resolverInterceptors =
+ EventResolverInterceptors.populateInterceptors(pluginPackages);
+ final EventResolverStringSubstitutor substitutor =
+ new EventResolverStringSubstitutor(configuration.getStrSubstitutor());
+
+ // Read event and stack trace element templates.
final String eventTemplate = readEventTemplate(builder);
+ final String stackTraceElementTemplate = readStackTraceElementTemplate(builder);
+
+ // Determine the max. string byte count.
final float maxByteCountPerChar = builder.charset.newEncoder().maxBytesPerChar();
final int maxStringByteCount =
Math.toIntExact(Math.round(Math.ceil(
maxByteCountPerChar * builder.maxStringLength)));
+
+ // Replace null event template additional fields with an empty array.
final EventTemplateAdditionalField[] eventTemplateAdditionalFields =
builder.eventTemplateAdditionalFields != null
? builder.eventTemplateAdditionalFields
: new EventTemplateAdditionalField[0];
+
+ // Create the resolver context.
final EventResolverContext resolverContext = EventResolverContext
.newBuilder()
.setConfiguration(configuration)
+ .setResolverFactoryByName(resolverFactoryByName)
+ .setResolverInterceptors(resolverInterceptors)
.setSubstitutor(substitutor)
.setCharset(charset)
.setJsonWriter(jsonWriter)
@@ -154,11 +155,14 @@ public class JsonTemplateLayout implements StringLayout {
.setTruncatedStringSuffix(builder.truncatedStringSuffix)
.setLocationInfoEnabled(builder.locationInfoEnabled)
.setStackTraceEnabled(builder.stackTraceEnabled)
- .setStackTraceElementObjectResolver(stackTraceElementObjectResolver)
+ .setStackTraceElementTemplate(stackTraceElementTemplate)
.setEventTemplateRootObjectKey(builder.eventTemplateRootObjectKey)
.setEventTemplateAdditionalFields(eventTemplateAdditionalFields)
.build();
+
+ // Compile the resolver template.
return TemplateResolvers.ofTemplate(resolverContext, eventTemplate);
+
}
private static String readEventTemplate(final Builder builder) {
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolver.java
index 4a716c8..9ff9b3f 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolver.java
@@ -19,7 +19,10 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-final class EndOfBatchResolver implements EventResolver {
+/**
+ * {@code endOfBatch} indicator resolver.
+ */
+public final class EndOfBatchResolver implements EventResolver {
private static final EndOfBatchResolver INSTANCE = new EndOfBatchResolver();
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolverFactory.java
index f059bbb..69d0079 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EndOfBatchResolverFactory.java
@@ -16,13 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class EndOfBatchResolverFactory implements EventResolverFactory<EndOfBatchResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
- private static final EndOfBatchResolverFactory INSTANCE = new EndOfBatchResolverFactory();
+/**
+ * {@link EndOfBatchResolver} factory.
+ */
+@Plugin(name = "EndOfBatchResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class EndOfBatchResolverFactory implements EventResolverFactory {
+
+ private static final EndOfBatchResolverFactory INSTANCE =
+ new EndOfBatchResolverFactory();
private EndOfBatchResolverFactory() {}
- static EndOfBatchResolverFactory getInstance() {
+ @PluginFactory
+ public static EndOfBatchResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventAdditionalFieldInterceptor.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventAdditionalFieldInterceptor.java
new file mode 100644
index 0000000..951d8df
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventAdditionalFieldInterceptor.java
@@ -0,0 +1,98 @@
+/*
+ * 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.layout.template.json.resolver;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.template.json.util.JsonReader;
+
+import java.util.Map;
+
+/**
+ * Interceptor to add {@link EventTemplateAdditionalField
+ * additional fields} after event template read.
+ */
+@Plugin(name = "EventAdditionalFieldInterceptor", category = TemplateResolverInterceptor.CATEGORY)
+public class EventAdditionalFieldInterceptor implements EventResolverInterceptor {
+
+ private static final EventAdditionalFieldInterceptor INSTANCE =
+ new EventAdditionalFieldInterceptor();
+
+ private EventAdditionalFieldInterceptor() {}
+
+ @PluginFactory
+ public static EventAdditionalFieldInterceptor getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Object processTemplateBeforeResolverInjection(
+ final EventResolverContext context,
+ final Object node) {
+
+ // Short-circuit if there are no additional fields.
+ final EventTemplateAdditionalField[] additionalFields =
+ context.getEventTemplateAdditionalFields();
+ if (additionalFields.length == 0) {
+ return node;
+ }
+
+ // Check that the root is an object node.
+ final Map<String, Object> objectNode;
+ try {
+ @SuppressWarnings("unchecked") final Map<String, Object> map = (Map<String, Object>) node;
+ objectNode = map;
+ } catch (final ClassCastException error) {
+ final String message = String.format(
+ "was expecting an object to merge additional fields: %s",
+ node.getClass().getName());
+ throw new IllegalArgumentException(message);
+ }
+
+ // Merge additional fields.
+ for (final EventTemplateAdditionalField additionalField : additionalFields) {
+ final String additionalFieldKey = additionalField.getKey();
+ final Object additionalFieldValue;
+ final EventTemplateAdditionalField.Format additionalFieldFormat =
+ additionalField.getFormat();
+ if (EventTemplateAdditionalField.Format.STRING.equals(additionalFieldFormat)) {
+ additionalFieldValue = additionalField.getValue();
+ } else if (EventTemplateAdditionalField.Format.JSON.equals(additionalFieldFormat)) {
+ try {
+ additionalFieldValue = JsonReader.read(additionalField.getValue());
+ } catch (final Exception error) {
+ final String message = String.format(
+ "failed reading JSON provided by additional field: %s",
+ additionalFieldKey);
+ throw new IllegalArgumentException(message, error);
+ }
+ } else {
+ final String message = String.format(
+ "unknown format %s for additional field: %s",
+ additionalFieldKey, additionalFieldFormat);
+ throw new IllegalArgumentException(message);
+ }
+ objectNode.put(additionalFieldKey, additionalFieldValue);
+ }
+
+ // Return the modified node.
+ return node;
+
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
index 8427d6d..f34adb7 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
@@ -18,4 +18,7 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-interface EventResolver extends TemplateResolver<LogEvent> {}
+/**
+ * {@link TemplateResolver} specialized for {@link LogEvent}s.
+ */
+public interface EventResolver extends TemplateResolver<LogEvent> {}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
index e1d2cb6..8ec2317 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
@@ -18,20 +18,31 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory;
+import org.apache.logging.log4j.util.Strings;
import java.nio.charset.Charset;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+/**
+ * {@link TemplateResolverContext} specialized for {@link LogEvent}s.
+ *
+ * @see EventResolver
+ * @see EventResolverFactory
+ */
public final class EventResolverContext implements TemplateResolverContext<LogEvent, EventResolverContext> {
private final Configuration configuration;
- private final StrSubstitutor substitutor;
+ private final Map<String, EventResolverFactory> resolverFactoryByName;
+
+ private final List<EventResolverInterceptor> resolverInterceptors;
+
+ private final EventResolverStringSubstitutor substitutor;
private final Charset charset;
@@ -47,14 +58,16 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
private final boolean stackTraceEnabled;
- private final TemplateResolver<Throwable> stackTraceObjectResolver;
+ private final String stackTraceElementTemplate;
private final String eventTemplateRootObjectKey;
- private final EventTemplateAdditionalField[] additionalFields;
+ private final EventTemplateAdditionalField[] eventTemplateAdditionalFields;
private EventResolverContext(final Builder builder) {
this.configuration = builder.configuration;
+ this.resolverFactoryByName = builder.resolverFactoryByName;
+ this.resolverInterceptors = builder.resolverInterceptors;
this.substitutor = builder.substitutor;
this.charset = builder.charset;
this.jsonWriter = builder.jsonWriter;
@@ -63,29 +76,32 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
this.truncatedStringSuffix = builder.truncatedStringSuffix;
this.locationInfoEnabled = builder.locationInfoEnabled;
this.stackTraceEnabled = builder.stackTraceEnabled;
- this.stackTraceObjectResolver = stackTraceEnabled
- ? new StackTraceObjectResolver(builder.stackTraceElementObjectResolver)
- : null;
+ this.stackTraceElementTemplate = builder.stackTraceElementTemplate;
this.eventTemplateRootObjectKey = builder.eventTemplateRootObjectKey;
- this.additionalFields = builder.eventTemplateAdditionalFields;
+ this.eventTemplateAdditionalFields = builder.eventTemplateAdditionalFields;
}
@Override
- public Class<EventResolverContext> getContextClass() {
+ public final Class<EventResolverContext> getContextClass() {
return EventResolverContext.class;
}
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
@Override
- public Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> getResolverFactoryByName() {
- return EventResolverFactories.getResolverFactoryByName();
+ public Map<String, EventResolverFactory> getResolverFactoryByName() {
+ return resolverFactoryByName;
}
- public Configuration getConfiguration() {
- return configuration;
+ @Override
+ public List<EventResolverInterceptor> getResolverInterceptors() {
+ return resolverInterceptors;
}
@Override
- public StrSubstitutor getSubstitutor() {
+ public EventResolverStringSubstitutor getSubstitutor() {
return substitutor;
}
@@ -98,36 +114,36 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
return jsonWriter;
}
- RecyclerFactory getRecyclerFactory() {
+ public RecyclerFactory getRecyclerFactory() {
return recyclerFactory;
}
- int getMaxStringByteCount() {
+ public int getMaxStringByteCount() {
return maxStringByteCount;
}
- String getTruncatedStringSuffix() {
+ public String getTruncatedStringSuffix() {
return truncatedStringSuffix;
}
- boolean isLocationInfoEnabled() {
+ public boolean isLocationInfoEnabled() {
return locationInfoEnabled;
}
- boolean isStackTraceEnabled() {
+ public boolean isStackTraceEnabled() {
return stackTraceEnabled;
}
- TemplateResolver<Throwable> getStackTraceObjectResolver() {
- return stackTraceObjectResolver;
+ public String getStackTraceElementTemplate() {
+ return stackTraceElementTemplate;
}
- String getEventTemplateRootObjectKey() {
+ public String getEventTemplateRootObjectKey() {
return eventTemplateRootObjectKey;
}
- EventTemplateAdditionalField[] getAdditionalFields() {
- return additionalFields;
+ public EventTemplateAdditionalField[] getEventTemplateAdditionalFields() {
+ return eventTemplateAdditionalFields;
}
public static Builder newBuilder() {
@@ -138,7 +154,11 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
private Configuration configuration;
- private StrSubstitutor substitutor;
+ private Map<String, EventResolverFactory> resolverFactoryByName;
+
+ private List<EventResolverInterceptor> resolverInterceptors;
+
+ private EventResolverStringSubstitutor substitutor;
private Charset charset;
@@ -154,7 +174,7 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
private boolean stackTraceEnabled;
- private TemplateResolver<StackTraceElement> stackTraceElementObjectResolver;
+ private String stackTraceElementTemplate;
private String eventTemplateRootObjectKey;
@@ -169,7 +189,19 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
return this;
}
- public Builder setSubstitutor(final StrSubstitutor substitutor) {
+ public Builder setResolverFactoryByName(
+ final Map<String, EventResolverFactory> resolverFactoryByName) {
+ this.resolverFactoryByName = resolverFactoryByName;
+ return this;
+ }
+
+ public Builder setResolverInterceptors(
+ final List<EventResolverInterceptor> resolverInterceptors) {
+ this.resolverInterceptors = resolverInterceptors;
+ return this;
+ }
+
+ public Builder setSubstitutor(final EventResolverStringSubstitutor substitutor) {
this.substitutor = substitutor;
return this;
}
@@ -194,10 +226,6 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
return this;
}
- public String getTruncatedStringSuffix() {
- return truncatedStringSuffix;
- }
-
public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) {
this.truncatedStringSuffix = truncatedStringSuffix;
return this;
@@ -213,9 +241,8 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
return this;
}
- public Builder setStackTraceElementObjectResolver(
- final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver) {
- this.stackTraceElementObjectResolver = stackTraceElementObjectResolver;
+ public Builder setStackTraceElementTemplate(final String stackTraceElementTemplate) {
+ this.stackTraceElementTemplate = stackTraceElementTemplate;
return this;
}
@@ -237,6 +264,11 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
private void validate() {
Objects.requireNonNull(configuration, "configuration");
+ Objects.requireNonNull(resolverFactoryByName, "resolverFactoryByName");
+ if (resolverFactoryByName.isEmpty()) {
+ throw new IllegalArgumentException("empty resolverFactoryByName");
+ }
+ Objects.requireNonNull(resolverInterceptors, "resolverInterceptors");
Objects.requireNonNull(substitutor, "substitutor");
Objects.requireNonNull(charset, "charset");
Objects.requireNonNull(jsonWriter, "jsonWriter");
@@ -246,11 +278,13 @@ public final class EventResolverContext implements TemplateResolverContext<LogEv
"was expecting maxStringByteCount > 0: " +
maxStringByteCount);
}
- if (stackTraceEnabled) {
- Objects.requireNonNull(
- stackTraceElementObjectResolver,
- "stackTraceElementObjectResolver");
+ Objects.requireNonNull(truncatedStringSuffix, "truncatedStringSuffix");
+ if (stackTraceEnabled && Strings.isBlank(stackTraceElementTemplate)) {
+ throw new IllegalArgumentException(
+ "stackTraceElementTemplate cannot be blank when stackTraceEnabled is set to true");
}
+ Objects.requireNonNull(stackTraceElementTemplate, "stackTraceElementTemplate");
+ Objects.requireNonNull(eventTemplateAdditionalFields, "eventTemplateAdditionalFields");
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactories.java
index 0bfcd76..b35cd22 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactories.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactories.java
@@ -18,51 +18,22 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-final class EventResolverFactories {
+/**
+ * Utility class for {@link EventResolverFactory}.
+ */
+public final class EventResolverFactories {
private EventResolverFactories() {}
- private static final Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> RESOLVER_FACTORY_BY_NAME =
- createResolverFactoryByName();
-
- private static Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> createResolverFactoryByName() {
-
- // Collect resolver factories.
- final List<EventResolverFactory<? extends EventResolver>> resolverFactories = Arrays.asList(
- ThreadContextDataResolverFactory.getInstance(),
- ThreadContextStackResolverFactory.getInstance(),
- EndOfBatchResolverFactory.getInstance(),
- ExceptionResolverFactory.getInstance(),
- ExceptionRootCauseResolverFactory.getInstance(),
- LevelResolverFactory.getInstance(),
- LoggerResolverFactory.getInstance(),
- MainMapResolverFactory.getInstance(),
- MapResolverFactory.getInstance(),
- MarkerResolverFactory.getInstance(),
- MessageResolverFactory.getInstance(),
- MessageParameterResolverFactory.getInstance(),
- PatternResolverFactory.getInstance(),
- SourceResolverFactory.getInstance(),
- ThreadResolverFactory.getInstance(),
- TimestampResolverFactory.getInstance());
-
- // Convert collection to map.
- final Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> resolverFactoryByName = new LinkedHashMap<>();
- for (final EventResolverFactory<? extends EventResolver> resolverFactory : resolverFactories) {
- resolverFactoryByName.put(resolverFactory.getName(), resolverFactory);
- }
- return Collections.unmodifiableMap(resolverFactoryByName);
-
- }
-
- static Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> getResolverFactoryByName() {
- return RESOLVER_FACTORY_BY_NAME;
+ public static Map<String, EventResolverFactory> populateResolverFactoryByName(
+ final List<String> pluginPackages) {
+ return TemplateResolverFactories.populateFactoryByName(
+ pluginPackages,
+ LogEvent.class,
+ EventResolverContext.class);
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactory.java
index 11c236e..2783bff 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverFactory.java
@@ -18,4 +18,23 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-interface EventResolverFactory<R extends TemplateResolver<LogEvent>> extends TemplateResolverFactory<LogEvent, EventResolverContext, R> {}
+/**
+ * {@link TemplateResolverFactory} specialized for {@link LogEvent}s.
+ *
+ * @see EventResolver
+ * @see EventResolverContext
+ */
+public interface EventResolverFactory
+ extends TemplateResolverFactory<LogEvent, EventResolverContext> {
+
+ @Override
+ default Class<LogEvent> getValueClass() {
+ return LogEvent.class;
+ }
+
+ @Override
+ default Class<EventResolverContext> getContextClass() {
+ return EventResolverContext.class;
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptor.java
similarity index 68%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptor.java
index 8427d6d..126ac46 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptor.java
@@ -18,4 +18,20 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-interface EventResolver extends TemplateResolver<LogEvent> {}
+/**
+ * {@link TemplateResolverInterceptor} specialized for {@link LogEvent}s.
+ */
+public interface EventResolverInterceptor
+ extends TemplateResolverInterceptor<LogEvent, EventResolverContext> {
+
+ @Override
+ default Class<LogEvent> getValueClass() {
+ return LogEvent.class;
+ }
+
+ @Override
+ default Class<EventResolverContext> getContextClass() {
+ return EventResolverContext.class;
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptors.java
similarity index 65%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptors.java
index 8427d6d..15b35f2 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverInterceptors.java
@@ -18,4 +18,21 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-interface EventResolver extends TemplateResolver<LogEvent> {}
+import java.util.List;
+
+/**
+ * Utility class for {@link EventResolverInterceptor}.
+ */
+public final class EventResolverInterceptors {
+
+ private EventResolverInterceptors() {}
+
+ public static List<EventResolverInterceptor> populateInterceptors(
+ final List<String> pluginPackages) {
+ return TemplateResolverInterceptors.populateInterceptors(
+ pluginPackages,
+ LogEvent.class,
+ EventResolverContext.class);
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverStringSubstitutor.java
similarity index 55%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverStringSubstitutor.java
index 37119ca..cb109af 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverStringSubstitutor.java
@@ -17,33 +17,35 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.util.Throwables;
-import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+
+import java.util.Objects;
/**
- * Exception root cause resolver.
- *
- * Note that this resolver is toggled by {@link
- * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
- *
- * @see ExceptionResolver
+ * {@link TemplateResolverStringSubstitutor} specialized for {@link LogEvent}s.
*/
-final class ExceptionRootCauseResolver extends ExceptionResolver {
+public final class EventResolverStringSubstitutor
+ implements TemplateResolverStringSubstitutor<LogEvent> {
+
+ private final StrSubstitutor substitutor;
- ExceptionRootCauseResolver(
- final EventResolverContext context,
- final TemplateResolverConfig config) {
- super(context, config);
+ public EventResolverStringSubstitutor(final StrSubstitutor substitutor) {
+ this.substitutor = Objects.requireNonNull(substitutor, "substitutor");
}
- static String getName() {
- return "exceptionRootCause";
+ @Override
+ public StrSubstitutor getInternalSubstitutor() {
+ return substitutor;
+ }
+
+ @Override
+ public boolean isStable() {
+ return false;
}
@Override
- Throwable extractThrowable(final LogEvent logEvent) {
- final Throwable thrown = logEvent.getThrown();
- return thrown != null ? Throwables.getRootCause(thrown) : null;
+ public String replace(final LogEvent logEvent, final String source) {
+ return substitutor.replace(logEvent, source);
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventRootObjectKeyInterceptor.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventRootObjectKeyInterceptor.java
new file mode 100644
index 0000000..f601c66
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventRootObjectKeyInterceptor.java
@@ -0,0 +1,53 @@
+/*
+ * 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.layout.template.json.resolver;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+
+import java.util.Collections;
+
+/**
+ * Interceptor to add a root object key to the event template.
+ *
+ * @see JsonTemplateLayout.Builder#getEventTemplateRootObjectKey()
+ */
+@Plugin(name = "EventRootObjectKeyInterceptor", category = TemplateResolverInterceptor.CATEGORY)
+public class EventRootObjectKeyInterceptor implements EventResolverInterceptor {
+
+ private static final EventRootObjectKeyInterceptor INSTANCE =
+ new EventRootObjectKeyInterceptor();
+
+ private EventRootObjectKeyInterceptor() {}
+
+ @PluginFactory
+ public static EventRootObjectKeyInterceptor getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Object processTemplateBeforeResolverInjection(
+ final EventResolverContext context,
+ final Object node) {
+ String eventTemplateRootObjectKey = context.getEventTemplateRootObjectKey();
+ return eventTemplateRootObjectKey != null
+ ? Collections.singletonMap(eventTemplateRootObjectKey, node)
+ : node;
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
index 4cdcd28..e2a538a 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
@@ -25,11 +25,17 @@ import org.apache.logging.log4j.status.StatusLogger;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Exception resolver.
+ * <p>
+ * Note that this resolver is toggled by {@link
+ * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean) stackTraceEnabled}
+ * layout configuration, which is by default populated from <tt>log4j.layout.jsonTemplate.stackTraceEnabled</tt>
+ * system property.
*
* <h3>Configuration</h3>
*
@@ -37,7 +43,11 @@ import java.util.regex.PatternSyntaxException;
* config = field , [ stringified ] , [ stackTrace ]
* field = "field" -> ( "className" | "message" | "stackTrace" )
*
- * stackTrace = "stackTrace" -> stringified
+ * stackTrace = "stackTrace" -> (
+ * [ stringified ]
+ * , [ elementTemplate ]
+ * )
+ *
* stringified = "stringified" -> ( boolean | truncation )
* truncation = "truncation" -> (
* [ suffix ]
@@ -47,6 +57,8 @@ import java.util.regex.PatternSyntaxException;
* suffix = "suffix" -> string
* pointMatcherStrings = "pointMatcherStrings" -> string[]
* pointMatcherRegexes = "pointMatcherRegexes" -> string[]
+ *
+ * elementTemplate = "elementTemplate" -> object
* </pre>
*
* <tt>stringified</tt> is set to <tt>false</tt> by default.
@@ -61,6 +73,28 @@ import java.util.regex.PatternSyntaxException;
* If a stringified stack trace truncation takes place, it will be indicated
* with <tt>suffix</tt>, which by default is set to the configured
* <tt>truncatedStringSuffix</tt> in the layout, unless explicitly provided.
+ * <p>
+ * <tt>elementTemplate</tt> is an object describing the template to be used
+ * while resolving the {@link StackTraceElement} array. If <tt>stringified</tt>
+ * is set to <tt>true</tt>, <tt>elementTemplate</tt> will be discarded. By
+ * default, <tt>elementTemplate</tt> is set to <tt>null</tt> and rather
+ * populated from the layout configuration. That is, the stack trace element
+ * template can also be provided using {@link JsonTemplateLayout.Builder#setStackTraceElementTemplate(String) stackTraceElementTemplate}
+ * and {@link JsonTemplateLayout.Builder#setStackTraceElementTemplateUri(String) setStackTraceElementTemplateUri}
+ * layout configuration parameters. The template to be employed is determined
+ * in the following order:
+ * <ol>
+ * <li><tt>elementTemplate</tt> provided in the resolver configuration
+ * <li><tt>stackTraceElementTemplate</tt> parameter from layout configuration
+ * (the default is populated from <tt>log4j.layout.jsonTemplate.stackTraceElementTemplate</tt>
+ * system property)
+ * <li><tt>stackTraceElementTemplateUri</tt> parameter from layout configuration
+ * (the default is populated from <tt>log4j.layout.jsonTemplate.stackTraceElementTemplateUri</tt>
+ * system property)
+ * </ol>
+ * <p>
+ * See {@link StackTraceElementResolver}
+ * for the list of available resolvers in a stack trace element template.
*
* <h3>Examples</h3>
*
@@ -95,7 +129,7 @@ import java.util.regex.PatternSyntaxException;
* </pre>
*
* Resolve the stack trace into a string field
- * such that the content will be truncated by the given point matcher:
+ * such that the content will be truncated after the given point matcher:
*
* <pre>
* {
@@ -112,11 +146,46 @@ import java.util.regex.PatternSyntaxException;
* }
* </pre>
*
+ * Resolve the stack trace into an object described by the provided stack trace
+ * element template:
+ *
+ * <pre>
+ * {
+ * "$resolver": "exception",
+ * "field": "stackTrace",
+ * "stackTrace": {
+ * "elementTemplate": {
+ * "class": {
+ * "$resolver": "stackTraceElement",
+ * "field": "className"
+ * },
+ * "method": {
+ * "$resolver": "stackTraceElement",
+ * "field": "methodName"
+ * },
+ * "file": {
+ * "$resolver": "stackTraceElement",
+ * "field": "fileName"
+ * },
+ * "line": {
+ * "$resolver": "stackTraceElement",
+ * "field": "lineNumber"
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
* @see JsonTemplateLayout.Builder#getTruncatedStringSuffix()
* @see JsonTemplateLayoutDefaults#getTruncatedStringSuffix()
+ * @see JsonTemplateLayout.Builder#getStackTraceElementTemplate()
+ * @see JsonTemplateLayoutDefaults#getStackTraceElementTemplate()
+ * @see JsonTemplateLayout.Builder#getStackTraceElementTemplateUri()
+ * @see JsonTemplateLayoutDefaults#getStackTraceElementTemplateUri()
* @see ExceptionRootCauseResolver
+ * @see StackTraceElementResolver
*/
-class ExceptionResolver implements EventResolver {
+public class ExceptionResolver implements EventResolver {
private static final Logger LOGGER = StatusLogger.getLogger();
@@ -138,13 +207,14 @@ class ExceptionResolver implements EventResolver {
final EventResolverContext context,
final TemplateResolverConfig config) {
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "className": return createClassNameResolver();
- case "message": return createMessageResolver();
- case "stackTrace": return createStackTraceResolver(context, config);
+ if ("className".equals(fieldName)) {
+ return createClassNameResolver();
+ } else if ("message".equals(fieldName)) {
+ return createMessageResolver();
+ } else if ("stackTrace".equals(fieldName)) {
+ return createStackTraceResolver(context, config);
}
throw new IllegalArgumentException("unknown field: " + config);
-
}
private EventResolver createClassNameResolver() {
@@ -180,7 +250,7 @@ class ExceptionResolver implements EventResolver {
final boolean stringified = isStackTraceStringified(config);
return stringified
? createStackTraceStringResolver(context, config)
- : createStackTraceObjectResolver(context);
+ : createStackTraceObjectResolver(context, config);
}
private static boolean isStackTraceStringified(
@@ -285,20 +355,71 @@ class ExceptionResolver implements EventResolver {
}
+ private static final Map<String, StackTraceElementResolverFactory> STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME;
+
+ static {
+ final StackTraceElementResolverFactory stackTraceElementResolverFactory =
+ StackTraceElementResolverFactory.getInstance();
+ STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME =
+ Collections.singletonMap(
+ stackTraceElementResolverFactory.getName(),
+ stackTraceElementResolverFactory);
+ }
+
private EventResolver createStackTraceObjectResolver(
- final EventResolverContext context) {
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final TemplateResolver<StackTraceElement> stackTraceElementResolver =
+ createStackTraceElementResolver(context, config);
+ final StackTraceObjectResolver stackTraceResolver =
+ new StackTraceObjectResolver(stackTraceElementResolver);
return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
- final Throwable exception = extractThrowable(logEvent);
- if (exception == null) {
+ final Throwable throwable = extractThrowable(logEvent);
+ if (throwable == null) {
jsonWriter.writeNull();
} else {
- context
- .getStackTraceObjectResolver()
- .resolve(exception, jsonWriter);
+ stackTraceResolver.resolve(throwable, jsonWriter);
}
};
}
+ private static TemplateResolver<StackTraceElement> createStackTraceElementResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final StackTraceElementResolverStringSubstitutor substitutor =
+ new StackTraceElementResolverStringSubstitutor(
+ context.getSubstitutor().getInternalSubstitutor());
+ final StackTraceElementResolverContext stackTraceElementResolverContext =
+ StackTraceElementResolverContext
+ .newBuilder()
+ .setResolverFactoryByName(STACK_TRACE_ELEMENT_RESOLVER_FACTORY_BY_NAME)
+ .setSubstitutor(substitutor)
+ .setJsonWriter(context.getJsonWriter())
+ .build();
+ final String stackTraceElementTemplate =
+ findEffectiveStackTraceElementTemplate(context, config);
+ return TemplateResolvers.ofTemplate(
+ stackTraceElementResolverContext,
+ stackTraceElementTemplate);
+ }
+
+ private static String findEffectiveStackTraceElementTemplate(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+
+ // First, check the template configured in the resolver configuration.
+ final Object stackTraceElementTemplateObject =
+ config.getObject(new String[]{"stackTrace", "elementTemplate"});
+ if (stackTraceElementTemplateObject != null) {
+ final JsonWriter jsonWriter = context.getJsonWriter();
+ return jsonWriter.use(() -> jsonWriter.writeValue(stackTraceElementTemplateObject));
+ }
+
+ // Otherwise, use the template provided in the context.
+ return context.getStackTraceElementTemplate();
+
+ }
+
Throwable extractThrowable(final LogEvent logEvent) {
return logEvent.getThrown();
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolverFactory.java
index 89ced7f..a58a4e7 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolverFactory.java
@@ -16,15 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class ExceptionResolverFactory
- implements EventResolverFactory<ExceptionResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link ExceptionResolver} factory.
+ */
+@Plugin(name = "ExceptionResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class ExceptionResolverFactory implements EventResolverFactory {
private static final ExceptionResolverFactory INSTANCE =
new ExceptionResolverFactory();
private ExceptionResolverFactory() {}
- static ExceptionResolverFactory getInstance() {
+ @PluginFactory
+ public static ExceptionResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
index 37119ca..9d630b7 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
@@ -22,13 +22,15 @@ import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
/**
* Exception root cause resolver.
- *
+ * <p>
* Note that this resolver is toggled by {@link
- * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
+ * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean) stackTraceEnabled}
+ * layout configuration, which is by default populated from <tt>log4j.layout.jsonTemplate.stackTraceEnabled</tt>
+ * system property.
*
* @see ExceptionResolver
*/
-final class ExceptionRootCauseResolver extends ExceptionResolver {
+public final class ExceptionRootCauseResolver extends ExceptionResolver {
ExceptionRootCauseResolver(
final EventResolverContext context,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolverFactory.java
index 7dcea5f..756b98c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolverFactory.java
@@ -16,13 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class ExceptionRootCauseResolverFactory implements EventResolverFactory<ExceptionRootCauseResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
- private static final ExceptionRootCauseResolverFactory INSTANCE = new ExceptionRootCauseResolverFactory();
+/**
+ * {@link ExceptionRootCauseResolver} factory.
+ */
+@Plugin(name = "ExceptionRootCauseResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class ExceptionRootCauseResolverFactory implements EventResolverFactory {
+
+ private static final ExceptionRootCauseResolverFactory INSTANCE =
+ new ExceptionRootCauseResolverFactory();
private ExceptionRootCauseResolverFactory() {}
- static ExceptionRootCauseResolverFactory getInstance() {
+ @PluginFactory
+ public static ExceptionRootCauseResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolver.java
index 2952eb4..cc04c4a 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolver.java
@@ -72,9 +72,9 @@ import java.util.stream.Collectors;
* }
* </pre>
*/
-final class LevelResolver implements EventResolver {
+public final class LevelResolver implements EventResolver {
- private static String[] SEVERITY_CODE_RESOLUTION_BY_STANDARD_LEVEL_ORDINAL;
+ private static final String[] SEVERITY_CODE_RESOLUTION_BY_STANDARD_LEVEL_ORDINAL;
static {
final int levelCount = Level.values().length;
@@ -113,21 +113,20 @@ final class LevelResolver implements EventResolver {
final TemplateResolverConfig config) {
final JsonWriter jsonWriter = context.getJsonWriter();
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "name": return createNameResolver(jsonWriter);
- case "severity": {
- final String severityFieldName =
- config.getString(new String[]{"severity", "field"});
- switch (severityFieldName) {
- case "keyword": return createSeverityKeywordResolver(jsonWriter);
- case "code": return SEVERITY_CODE_RESOLVER;
- default:
- throw new IllegalArgumentException(
- "unknown severity field: " + config);
- }
+ if ("name".equals(fieldName)) {
+ return createNameResolver(jsonWriter);
+ } else if ("severity".equals(fieldName)) {
+ final String severityFieldName =
+ config.getString(new String[]{"severity", "field"});
+ if ("keyword".equals(severityFieldName)) {
+ return createSeverityKeywordResolver(jsonWriter);
+ } else if ("code".equals(severityFieldName)) {
+ return SEVERITY_CODE_RESOLVER;
}
- default: throw new IllegalArgumentException("unknown field: " + config);
+ throw new IllegalArgumentException(
+ "unknown severity field: " + config);
}
+ throw new IllegalArgumentException("unknown field: " + config);
}
private static EventResolver createNameResolver(
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolverFactory.java
index 2e8dc60..819dd87 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LevelResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class LevelResolverFactory implements EventResolverFactory<LevelResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link LevelResolver} factory.
+ */
+@Plugin(name = "LevelResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class LevelResolverFactory implements EventResolverFactory {
private static final LevelResolverFactory INSTANCE = new LevelResolverFactory();
private LevelResolverFactory() {}
- static LevelResolverFactory getInstance() {
+ @PluginFactory
+ public static LevelResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolver.java
index 6a47531..13088fc 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolver.java
@@ -48,7 +48,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
* }
* </pre>
*/
-final class LoggerResolver implements EventResolver {
+public final class LoggerResolver implements EventResolver {
private static final EventResolver NAME_RESOLVER =
(final LogEvent logEvent, final JsonWriter jsonWriter) -> {
@@ -71,9 +71,10 @@ final class LoggerResolver implements EventResolver {
private static EventResolver createInternalResolver(
final TemplateResolverConfig config) {
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "name": return NAME_RESOLVER;
- case "fqcn": return FQCN_RESOLVER;
+ if ("name".equals(fieldName)) {
+ return NAME_RESOLVER;
+ } else if ("fqcn".equals(fieldName)) {
+ return FQCN_RESOLVER;
}
throw new IllegalArgumentException("unknown field: " + config);
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java
index 08b6fb9..7e889f9 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class LoggerResolverFactory implements EventResolverFactory<LoggerResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link LoggerResolver} factory.
+ */
+@Plugin(name = "LoggerResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class LoggerResolverFactory implements EventResolverFactory {
private static final LoggerResolverFactory INSTANCE = new LoggerResolverFactory();
private LoggerResolverFactory() {}
- static LoggerResolverFactory getInstance() {
+ @PluginFactory
+ public static LoggerResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolver.java
index 514dd1e..a3edafd 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolver.java
@@ -53,7 +53,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
*
* @see MainMapResolver
*/
-final class MainMapResolver implements EventResolver {
+public final class MainMapResolver implements EventResolver {
private static final MainMapLookup MAIN_MAP_LOOKUP = new MainMapLookup();
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolverFactory.java
index ee0ab3f..917b4d6 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MainMapResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class MainMapResolverFactory implements EventResolverFactory<MainMapResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link MainMapResolver} factory.
+ */
+@Plugin(name = "MainMapResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class MainMapResolverFactory implements EventResolverFactory {
private static final MainMapResolverFactory INSTANCE = new MainMapResolverFactory();
private MainMapResolverFactory() {}
- static MainMapResolverFactory getInstance() {
+ @PluginFactory
+ public static MainMapResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
index 5cc07eb..d5801a4 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
@@ -26,7 +26,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
*
* @see ReadOnlyStringMapResolver
*/
-final class MapResolver extends ReadOnlyStringMapResolver {
+public final class MapResolver extends ReadOnlyStringMapResolver {
MapResolver(
final EventResolverContext context,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
index 53092c9..a663d9e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class MapResolverFactory implements EventResolverFactory<MapResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link MapResolver} factory.
+ */
+@Plugin(name = "MapResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class MapResolverFactory implements EventResolverFactory {
private static final MapResolverFactory INSTANCE = new MapResolverFactory();
private MapResolverFactory() {}
- static MapResolverFactory getInstance() {
+ @PluginFactory
+ public static MapResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java
index 1448444..fc1c30c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolver.java
@@ -40,7 +40,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
* }
* </pre>
*/
-final class MarkerResolver implements EventResolver {
+public final class MarkerResolver implements EventResolver {
private static final TemplateResolver<LogEvent> NAME_RESOLVER =
(final LogEvent logEvent, final JsonWriter jsonWriter) -> {
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverFactory.java
index 41b2126..4a10b4d 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MarkerResolverFactory.java
@@ -16,16 +16,24 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class MarkerResolverFactory implements EventResolverFactory<MarkerResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link MarkerResolver} factory.
+ */
+@Plugin(name = "MarkerResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class MarkerResolverFactory implements EventResolverFactory {
private static final MarkerResolverFactory INSTANCE = new MarkerResolverFactory();
- static MarkerResolverFactory getInstance() {
+ private MarkerResolverFactory() {}
+
+ @PluginFactory
+ public static MarkerResolverFactory getInstance() {
return INSTANCE;
}
- private MarkerResolverFactory() {}
-
@Override
public String getName() {
return MarkerResolver.getName();
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 238f523..9b8182e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
@@ -71,7 +71,7 @@ import org.apache.logging.log4j.message.ParameterVisitable;
* }
* </pre>
*/
-final class MessageParameterResolver implements EventResolver {
+public final class MessageParameterResolver implements EventResolver {
private final Recycler<ParameterConsumerState> parameterConsumerStateRecycler;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolverFactory.java
index 055071c..15c398c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolverFactory.java
@@ -16,13 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class MessageParameterResolverFactory implements EventResolverFactory<MessageParameterResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
- private static final MessageParameterResolverFactory INSTANCE = new MessageParameterResolverFactory();
+/**
+ * {@link MessageParameterResolver} factory.
+ */
+@Plugin(name = "MessageParameterResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class MessageParameterResolverFactory implements EventResolverFactory {
+
+ private static final MessageParameterResolverFactory INSTANCE =
+ new MessageParameterResolverFactory();
private MessageParameterResolverFactory() {}
- static MessageParameterResolverFactory getInstance() {
+ @PluginFactory
+ public static MessageParameterResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
index f42dbab..2975e66 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
@@ -79,7 +79,7 @@ import org.apache.logging.log4j.util.StringBuilderFormattable;
* that both emitted JSONs are of type <tt>object</tt> and have no
* type-conflicting fields.
*/
-final class MessageResolver implements EventResolver {
+public final class MessageResolver implements EventResolver {
private static final String[] FORMATS = { "JSON" };
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverFactory.java
index 5e2a3db..334edfc 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class MessageResolverFactory implements EventResolverFactory<MessageResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * @see MessageResolver
+ */
+@Plugin(name = "MessageResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class MessageResolverFactory implements EventResolverFactory {
private static final MessageResolverFactory INSTANCE = new MessageResolverFactory();
private MessageResolverFactory() {}
- static MessageResolverFactory getInstance() {
+ @PluginFactory
+ public static MessageResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
index 5bfc4b4..8cd2991 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
@@ -51,7 +51,7 @@ import java.util.Optional;
* }
* </pre>
*/
-final class PatternResolver implements EventResolver {
+public final class PatternResolver implements EventResolver {
private final BiConsumer<StringBuilder, LogEvent> emitter;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolverFactory.java
index 95c9a95..9c105b4 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class PatternResolverFactory implements EventResolverFactory<PatternResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link PatternResolver} factory.
+ */
+@Plugin(name = "PatternResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class PatternResolverFactory implements EventResolverFactory {
private static final PatternResolverFactory INSTANCE = new PatternResolverFactory();
private PatternResolverFactory() {}
- static PatternResolverFactory getInstance() {
+ @PluginFactory
+ public static PatternResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolver.java
index b4e63e3..36e5f29 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolver.java
@@ -22,10 +22,11 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
/**
* Resolver for the {@link StackTraceElement} returned by {@link LogEvent#getSource()}.
- *
+ * <p>
* Note that this resolver is toggled by {@link
- * JsonTemplateLayout.Builder#setLocationInfoEnabled(boolean)}
- * method.
+ * JsonTemplateLayout.Builder#setLocationInfoEnabled(boolean) locationInfoEnabled}
+ * layout configuration, which is by default populated from {@code log4j.layout.jsonTemplate.locationInfoEnabled}
+ * system property.
*
* <h3>Configuration</h3>
*
@@ -48,7 +49,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
* }
* </pre>
*/
-final class SourceResolver implements EventResolver {
+public final class SourceResolver implements EventResolver {
private static final EventResolver NULL_RESOLVER =
(final LogEvent value, final JsonWriter jsonWriter) ->
@@ -116,11 +117,14 @@ final class SourceResolver implements EventResolver {
return NULL_RESOLVER;
}
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "className": return CLASS_NAME_RESOLVER;
- case "fileName": return FILE_NAME_RESOLVER;
- case "lineNumber": return LINE_NUMBER_RESOLVER;
- case "methodName": return METHOD_NAME_RESOLVER;
+ if ("className".equals(fieldName)) {
+ return CLASS_NAME_RESOLVER;
+ } else if ("fileName".equals(fieldName)) {
+ return FILE_NAME_RESOLVER;
+ } else if ("lineNumber".equals(fieldName)) {
+ return LINE_NUMBER_RESOLVER;
+ } else if ("methodName".equals(fieldName)) {
+ return METHOD_NAME_RESOLVER;
}
throw new IllegalArgumentException("unknown field: " + config);
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolverFactory.java
index 65a78d0..223fbfa 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/SourceResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class SourceResolverFactory implements EventResolverFactory<SourceResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link SourceResolver} factory.
+ */
+@Plugin(name = "SourceResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class SourceResolverFactory implements EventResolverFactory {
private static final SourceResolverFactory INSTANCE = new SourceResolverFactory();
private SourceResolverFactory() {}
- static SourceResolverFactory getInstance() {
+ @PluginFactory
+ public static SourceResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverContext.java
deleted file mode 100644
index 31493fd..0000000
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverContext.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.layout.template.json.resolver;
-
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-
-import java.util.Map;
-import java.util.Objects;
-
-public final class StackTraceElementObjectResolverContext
- implements TemplateResolverContext<StackTraceElement, StackTraceElementObjectResolverContext> {
-
- private final StrSubstitutor substitutor;
-
- private final JsonWriter jsonWriter;
-
- private StackTraceElementObjectResolverContext(final Builder builder) {
- this.substitutor = builder.substitutor;
- this.jsonWriter = builder.jsonWriter;
- }
-
- @Override
- public Class<StackTraceElementObjectResolverContext> getContextClass() {
- return StackTraceElementObjectResolverContext.class;
- }
-
- @Override
- public Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> getResolverFactoryByName() {
- return StackTraceElementObjectResolverFactories.getResolverFactoryByName();
- }
-
- @Override
- public StrSubstitutor getSubstitutor() {
- return substitutor;
- }
-
- @Override
- public JsonWriter getJsonWriter() {
- return jsonWriter;
- }
-
- public static Builder newBuilder() {
- return new Builder();
- }
-
- public static class Builder {
-
- private StrSubstitutor substitutor;
-
- private JsonWriter jsonWriter;
-
- private Builder() {
- // Do nothing.
- }
-
- public Builder setSubstitutor(final StrSubstitutor substitutor) {
- this.substitutor = substitutor;
- return this;
- }
-
- public Builder setJsonWriter(final JsonWriter jsonWriter) {
- this.jsonWriter = jsonWriter;
- return this;
- }
-
- public StackTraceElementObjectResolverContext build() {
- validate();
- return new StackTraceElementObjectResolverContext(this);
- }
-
- private void validate() {
- Objects.requireNonNull(substitutor, "substitutor");
- Objects.requireNonNull(jsonWriter, "jsonWriter");
- }
-
- }
-
-}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactories.java
deleted file mode 100644
index cf76012..0000000
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactories.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.layout.template.json.resolver;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-final class StackTraceElementObjectResolverFactories {
-
- private StackTraceElementObjectResolverFactories() {}
-
- private static final Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> RESOLVER_FACTORY_BY_NAME =
- createResolverFactoryByName();
-
- private static Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> createResolverFactoryByName() {
- final Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> resolverFactoryByName = new LinkedHashMap<>();
- final StackTraceElementObjectResolverFactory stackTraceElementObjectResolverFactory = StackTraceElementObjectResolverFactory.getInstance();
- resolverFactoryByName.put(stackTraceElementObjectResolverFactory.getName(), stackTraceElementObjectResolverFactory);
- return Collections.unmodifiableMap(resolverFactoryByName);
- }
-
- static Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> getResolverFactoryByName() {
- return RESOLVER_FACTORY_BY_NAME;
- }
-
-}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolver.java
similarity index 84%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolver.java
rename to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolver.java
index 3d70082..467c92b 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolver.java
@@ -37,12 +37,12 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
*
* <pre>
* {
- * "$resolver": "source",
+ * "$resolver": "stackTraceElement",
* "field": "lineNumber"
* }
* </pre>
*/
-final class StackTraceElementObjectResolver implements TemplateResolver<StackTraceElement> {
+final class StackTraceElementResolver implements TemplateResolver<StackTraceElement> {
private static final TemplateResolver<StackTraceElement> CLASS_NAME_RESOLVER =
(final StackTraceElement stackTraceElement, final JsonWriter jsonWriter) ->
@@ -62,26 +62,29 @@ final class StackTraceElementObjectResolver implements TemplateResolver<StackTra
private final TemplateResolver<StackTraceElement> internalResolver;
- StackTraceElementObjectResolver(final TemplateResolverConfig config) {
+ StackTraceElementResolver(final TemplateResolverConfig config) {
this.internalResolver = createInternalResolver(config);
}
+ static String getName() {
+ return "stackTraceElement";
+ }
+
private TemplateResolver<StackTraceElement> createInternalResolver(
final TemplateResolverConfig config) {
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "className": return CLASS_NAME_RESOLVER;
- case "methodName": return METHOD_NAME_RESOLVER;
- case "fileName": return FILE_NAME_RESOLVER;
- case "lineNumber": return LINE_NUMBER_RESOLVER;
+ if ("className".equals(fieldName)) {
+ return CLASS_NAME_RESOLVER;
+ } else if ("methodName".equals(fieldName)) {
+ return METHOD_NAME_RESOLVER;
+ } else if ("fileName".equals(fieldName)) {
+ return FILE_NAME_RESOLVER;
+ } else if ("lineNumber".equals(fieldName)) {
+ return LINE_NUMBER_RESOLVER;
}
throw new IllegalArgumentException("unknown field: " + config);
}
- static String getName() {
- return "stackTraceElement";
- }
-
@Override
public void resolve(
final StackTraceElement stackTraceElement,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverContext.java
new file mode 100644
index 0000000..fbe8a06
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverContext.java
@@ -0,0 +1,121 @@
+/*
+ * 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.layout.template.json.resolver;
+
+import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * {@link TemplateResolverContext} specialized for {@link StackTraceElement}s.
+ *
+ * @see StackTraceElementResolver
+ * @see StackTraceElementResolverFactory
+ */
+final class StackTraceElementResolverContext
+ implements TemplateResolverContext<StackTraceElement, StackTraceElementResolverContext> {
+
+ private final Map<String, StackTraceElementResolverFactory> resolverFactoryByName;
+
+ private final StackTraceElementResolverStringSubstitutor substitutor;
+
+ private final JsonWriter jsonWriter;
+
+ private StackTraceElementResolverContext(final Builder builder) {
+ this.resolverFactoryByName = builder.resolverFactoryByName;
+ this.substitutor = builder.substitutor;
+ this.jsonWriter = builder.jsonWriter;
+ }
+
+ @Override
+ public final Class<StackTraceElementResolverContext> getContextClass() {
+ return StackTraceElementResolverContext.class;
+ }
+
+ @Override
+ public Map<String, StackTraceElementResolverFactory> getResolverFactoryByName() {
+ return resolverFactoryByName;
+ }
+
+ @Override
+ public List<? extends TemplateResolverInterceptor<StackTraceElement, StackTraceElementResolverContext>> getResolverInterceptors() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public StackTraceElementResolverStringSubstitutor getSubstitutor() {
+ return substitutor;
+ }
+
+ @Override
+ public JsonWriter getJsonWriter() {
+ return jsonWriter;
+ }
+
+ static Builder newBuilder() {
+ return new Builder();
+ }
+
+ static class Builder {
+
+ private Map<String, StackTraceElementResolverFactory> resolverFactoryByName;
+
+ private StackTraceElementResolverStringSubstitutor substitutor;
+
+ private JsonWriter jsonWriter;
+
+ private Builder() {
+ // Do nothing.
+ }
+
+ Builder setResolverFactoryByName(
+ final Map<String, StackTraceElementResolverFactory> resolverFactoryByName) {
+ this.resolverFactoryByName = resolverFactoryByName;
+ return this;
+ }
+
+ Builder setSubstitutor(
+ final StackTraceElementResolverStringSubstitutor substitutor) {
+ this.substitutor = substitutor;
+ return this;
+ }
+
+ Builder setJsonWriter(final JsonWriter jsonWriter) {
+ this.jsonWriter = jsonWriter;
+ return this;
+ }
+
+ StackTraceElementResolverContext build() {
+ validate();
+ return new StackTraceElementResolverContext(this);
+ }
+
+ private void validate() {
+ Objects.requireNonNull(resolverFactoryByName, "resolverFactoryByName");
+ if (resolverFactoryByName.isEmpty()) {
+ throw new IllegalArgumentException("empty resolverFactoryByName");
+ }
+ Objects.requireNonNull(substitutor, "substitutor");
+ Objects.requireNonNull(jsonWriter, "jsonWriter");
+ }
+
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverFactory.java
similarity index 56%
rename from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactory.java
rename to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverFactory.java
index aa64f1c..02c96b2 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementObjectResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverFactory.java
@@ -16,28 +16,41 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class StackTraceElementObjectResolverFactory
- implements TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, StackTraceElementObjectResolver> {
+/**
+ * {@link StackTraceElementResolver} factory.
+ */
+final class StackTraceElementResolverFactory
+ implements TemplateResolverFactory<StackTraceElement, StackTraceElementResolverContext> {
- private static final StackTraceElementObjectResolverFactory INSTANCE =
- new StackTraceElementObjectResolverFactory();
+ private static final StackTraceElementResolverFactory INSTANCE =
+ new StackTraceElementResolverFactory();
- private StackTraceElementObjectResolverFactory() {}
+ private StackTraceElementResolverFactory() {}
- public static StackTraceElementObjectResolverFactory getInstance() {
+ static StackTraceElementResolverFactory getInstance() {
return INSTANCE;
}
@Override
+ public Class<StackTraceElement> getValueClass() {
+ return StackTraceElement.class;
+ }
+
+ @Override
+ public Class<StackTraceElementResolverContext> getContextClass() {
+ return StackTraceElementResolverContext.class;
+ }
+
+ @Override
public String getName() {
- return StackTraceElementObjectResolver.getName();
+ return StackTraceElementResolver.getName();
}
@Override
- public StackTraceElementObjectResolver create(
- final StackTraceElementObjectResolverContext context,
+ public StackTraceElementResolver create(
+ final StackTraceElementResolverContext context,
final TemplateResolverConfig config) {
- return new StackTraceElementObjectResolver(config);
+ return new StackTraceElementResolver(config);
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverStringSubstitutor.java
similarity index 53%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverStringSubstitutor.java
index fbb0da4..fb59c31 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceElementResolverStringSubstitutor.java
@@ -17,18 +17,35 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import java.util.Map;
+import java.util.Objects;
-interface TemplateResolverContext<V, C extends TemplateResolverContext<V, C>> {
-
- Class<C> getContextClass();
-
- Map<String, TemplateResolverFactory<V, C, ? extends TemplateResolver<V>>> getResolverFactoryByName();
-
- StrSubstitutor getSubstitutor();
-
- JsonWriter getJsonWriter();
+/**
+ * {@link TemplateResolverStringSubstitutor} specialized for {@link StackTraceElement}s.
+ */
+final class StackTraceElementResolverStringSubstitutor
+ implements TemplateResolverStringSubstitutor<StackTraceElement> {
+
+ private final StrSubstitutor substitutor;
+
+ StackTraceElementResolverStringSubstitutor(
+ final StrSubstitutor substitutor) {
+ this.substitutor = Objects.requireNonNull(substitutor, "substitutor");
+ }
+
+ @Override
+ public StrSubstitutor getInternalSubstitutor() {
+ return substitutor;
+ }
+
+ @Override
+ public boolean isStable() {
+ return true;
+ }
+
+ @Override
+ public String replace(final StackTraceElement ignored, final String source) {
+ return substitutor.replace(null, source);
+ }
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceObjectResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceObjectResolver.java
index d20ca37..c7279d1 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceObjectResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceObjectResolver.java
@@ -18,6 +18,9 @@ package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+/**
+ * Exception stack trace to JSON object resolver used by {@link ExceptionResolver}.
+ */
final class StackTraceObjectResolver implements StackTraceResolver {
private final TemplateResolver<StackTraceElement> stackTraceElementResolver;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceResolver.java
index 824b6da..697480a 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceResolver.java
@@ -16,4 +16,7 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
+/**
+ * {@link TemplateResolver} specialized for {@link Throwable}s.
+ */
interface StackTraceResolver extends TemplateResolver<Throwable> {}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index 61be39e..0dee8d0 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -26,6 +26,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+/**
+ * Exception stack trace to JSON string resolver used by {@link ExceptionResolver}.
+ */
final class StackTraceStringResolver implements StackTraceResolver {
private final Recycler<TruncatingBufferedPrintWriter> writerRecycler;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverConfig.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverConfig.java
index d181b87..1ca0c9b 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverConfig.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverConfig.java
@@ -20,7 +20,42 @@ import org.apache.logging.log4j.layout.template.json.util.MapAccessor;
import java.util.Map;
-class TemplateResolverConfig extends MapAccessor {
+/**
+ * Accessor to the resolver configuration JSON object read from the template.
+ * {@link TemplateResolver Template resolvers} can use this class to
+ * read the configuration associated with them.
+ * <p>
+ * For instance, given the following template:
+ * <pre>
+ * {
+ * "@version": 1,
+ * "message": {
+ * "$resolver": "message",
+ * "stringified": true
+ * },
+ * "level": {
+ * "$resolver": "level",
+ * "field": "severity",
+ * "severity": {
+ * "field": "code"
+ * }
+ * }
+ * }
+ * </pre>
+ * {@link LevelResolverFactory#create(EventResolverContext, TemplateResolverConfig)}
+ * will be called with a {@link TemplateResolverConfig} accessor to the
+ * following configuration JSON object block:
+ * <pre>
+ * {
+ * "$resolver": "level",
+ * "field": "severity",
+ * "severity": {
+ * "field": "code"
+ * }
+ * }
+ * </pre>
+ */
+public class TemplateResolverConfig extends MapAccessor {
TemplateResolverConfig(final Map<String, Object> map) {
super(map);
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
index fbb0da4..7f8cbb8 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
@@ -16,19 +16,45 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+import java.util.List;
import java.util.Map;
+/**
+ * Context used to compile a template and passed to
+ * {@link TemplateResolverFactory#create(TemplateResolverContext, TemplateResolverConfig)
+ * template resolver factory creator}s.
+ *
+ * @param <V> type of the value passed to the resolver as input
+ * @param <C> type of the context passed to the {@link TemplateResolverFactory resolver factory}
+ *
+ * @see TemplateResolverFactory
+ */
interface TemplateResolverContext<V, C extends TemplateResolverContext<V, C>> {
Class<C> getContextClass();
- Map<String, TemplateResolverFactory<V, C, ? extends TemplateResolver<V>>> getResolverFactoryByName();
+ Map<String, ? extends TemplateResolverFactory<V, C>> getResolverFactoryByName();
- StrSubstitutor getSubstitutor();
+ List<? extends TemplateResolverInterceptor<V, C>> getResolverInterceptors();
+
+ TemplateResolverStringSubstitutor<V> getSubstitutor();
JsonWriter getJsonWriter();
+ /**
+ * Process the read template before compiler (i.e.,
+ * {@link TemplateResolvers#ofTemplate(TemplateResolverContext, String)}
+ * starts injecting resolvers.
+ * <p>
+ * This is the right place to introduce, say, contextual additional fields.
+ *
+ * @param node the root object of the read template
+ * @return the root object of the template to be compiled
+ */
+ default Object processTemplateBeforeResolverInjection(Object node) {
+ return node;
+ }
+
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
new file mode 100644
index 0000000..83270ab
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
@@ -0,0 +1,130 @@
+package org.apache.logging.log4j.layout.template.json.resolver;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.config.plugins.util.PluginUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for {@link TemplateResolverFactory}.
+ */
+public final class TemplateResolverFactories {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private TemplateResolverFactories() {}
+
+ /**
+ * Populates plugins implementing
+ * {@link TemplateResolverFactory TemplateResolverFactory<V, C>},
+ * where {@code V} and {@code C} denote the value and context class types,
+ * respectively.
+ */
+ public static <V, C extends TemplateResolverContext<V, C>, F extends TemplateResolverFactory<V, C>> Map<String, F> populateFactoryByName(
+ final List<String> pluginPackages,
+ final Class<V> valueClass,
+ final Class<C> contextClass) {
+
+ // Populate template resolver factories.
+ final Map<String, PluginType<?>> pluginTypeByName =
+ PluginUtil.collectPluginsByCategoryAndPackage(
+ TemplateResolverFactory.CATEGORY,
+ pluginPackages);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(
+ "found {} plugins of category \"{}\": {}",
+ pluginTypeByName.size(),
+ TemplateResolverFactory.CATEGORY,
+ pluginTypeByName.keySet());
+ }
+
+ // Filter matching resolver factories.
+ final Map<String, F> factoryByName =
+ populateFactoryByName(pluginTypeByName, valueClass, contextClass);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(
+ "matched {} resolver factories out of {} for value class {} and context class {}: {}",
+ factoryByName.size(),
+ pluginTypeByName.size(),
+ valueClass,
+ contextClass,
+ factoryByName.keySet());
+ }
+ return factoryByName;
+
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>, F extends TemplateResolverFactory<V, C>> Map<String, F> populateFactoryByName(
+ final Map<String, PluginType<?>> pluginTypeByName,
+ final Class<V> valueClass,
+ final Class<C> contextClass) {
+ final Map<String, F> factoryByName = new LinkedHashMap<>();
+ final Set<String> pluginNames = pluginTypeByName.keySet();
+ for (final String pluginName : pluginNames) {
+ final PluginType<?> pluginType = pluginTypeByName.get(pluginName);
+ final Class<?> pluginClass = pluginType.getPluginClass();
+ final boolean pluginClassMatched =
+ TemplateResolverFactory.class.isAssignableFrom(pluginClass);
+ if (pluginClassMatched) {
+ final TemplateResolverFactory<?, ?> rawFactory =
+ instantiateFactory(pluginName, pluginClass);
+ final F factory = castFactory(valueClass, contextClass, rawFactory);
+ if (factory != null) {
+ addFactory(factoryByName, factory);
+ }
+ }
+ }
+ return factoryByName;
+ }
+
+ private static TemplateResolverFactory<?, ?> instantiateFactory(
+ final String pluginName,
+ final Class<?> pluginClass) {
+ try {
+ return (TemplateResolverFactory<?, ?>)
+ PluginUtil.instantiatePlugin(pluginClass);
+ } catch (final Exception error) {
+ final String message = String.format(
+ "failed instantiating resolver factory plugin %s of name %s",
+ pluginClass, pluginName);
+ throw new RuntimeException(message, error);
+ }
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>, F extends TemplateResolverFactory<V, C>> F castFactory(
+ final Class<V> valueClass,
+ final Class<C> contextClass,
+ final TemplateResolverFactory<?, ?> factory) {
+ final Class<?> factoryValueClass = factory.getValueClass();
+ final Class<?> factoryContextClass = factory.getContextClass();
+ final boolean factoryValueClassMatched =
+ valueClass.isAssignableFrom(factoryValueClass);
+ final boolean factoryContextClassMatched =
+ contextClass.isAssignableFrom(factoryContextClass);
+ if (factoryValueClassMatched && factoryContextClassMatched) {
+ @SuppressWarnings("unchecked")
+ final F typedFactory = (F) factory;
+ return typedFactory;
+ }
+ return null;
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>, F extends TemplateResolverFactory<V, C>> void addFactory(
+ final Map<String, F> factoryByName,
+ final F factory) {
+ final String factoryName = factory.getName();
+ final F conflictingFactory = factoryByName.putIfAbsent(factoryName, factory);
+ if (conflictingFactory != null) {
+ final String message = String.format(
+ "found resolver factories with overlapping names: %s (%s and %s)",
+ factoryName, conflictingFactory, factory);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
index a2c8216..93cf611 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
@@ -16,10 +16,31 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-interface TemplateResolverFactory<V, C extends TemplateResolverContext<V, C>, R extends TemplateResolver<V>> {
+/**
+ * {@link TemplateResolver} factory.
+ *
+ * @param <V> type of the value passed to the {@link TemplateResolver resolver}
+ * @param <C> type of the context passed to the {@link TemplateResolverFactory#create(TemplateResolverContext, TemplateResolverConfig)} creator}
+ */
+public interface TemplateResolverFactory<V, C extends TemplateResolverContext<V, C>> {
+
+ /**
+ * Main plugin category for {@link TemplateResolverFactory} implementations.
+ */
+ String CATEGORY = "JsonTemplateResolverFactory";
+
+ /**
+ * The targeted value class.
+ */
+ Class<V> getValueClass();
+
+ /**
+ * The targeted {@link TemplateResolverContext} class.
+ */
+ Class<C> getContextClass();
String getName();
- R create(C context, TemplateResolverConfig config);
+ TemplateResolver<V> create(C context, TemplateResolverConfig config);
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java
new file mode 100644
index 0000000..4989521
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.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.layout.template.json.resolver;
+
+/**
+ * Main {@link TemplateResolver} compilation interception interface.
+ *
+ * @param <V> type of the value passed to the {@link TemplateResolver resolver}
+ * @param <C> type of the context employed
+ */
+public interface TemplateResolverInterceptor<V, C extends TemplateResolverContext<V, C>> {
+
+ /**
+ * Main plugin category for {@link TemplateResolverInterceptor} implementations.
+ */
+ String CATEGORY = "JsonTemplateResolverInterceptor";
+
+ /**
+ * The targeted value class.
+ */
+ Class<V> getValueClass();
+
+ /**
+ * The targeted {@link TemplateResolverContext} class.
+ */
+ Class<C> getContextClass();
+
+ /**
+ * Intercept the read template before compiler (i.e.,
+ * {@link TemplateResolvers#ofTemplate(TemplateResolverContext, String)}
+ * starts injecting resolvers.
+ * <p>
+ * This is the right place to introduce, say, contextual additional fields.
+ *
+ * @param node the root object of the read template
+ * @return the root object of the template to be compiled
+ */
+ default Object processTemplateBeforeResolverInjection(C context, Object node) {
+ return node;
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
new file mode 100644
index 0000000..126469d
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
@@ -0,0 +1,131 @@
+/*
+ * 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.layout.template.json.resolver;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.config.plugins.util.PluginUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for {@link TemplateResolverInterceptor}.
+ */
+public class TemplateResolverInterceptors {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private TemplateResolverInterceptors() {}
+
+ /**
+ * Populates plugins implementing
+ * {@link TemplateResolverInterceptor TemplateResolverInterceptor<V, C>},
+ * where {@code V} and {@code C} denote the value and context class types,
+ * respectively.
+ */
+ public static <V, C extends TemplateResolverContext<V, C>, I extends TemplateResolverInterceptor<V, C>> List<I> populateInterceptors(
+ final List<String> pluginPackages,
+ final Class<V> valueClass,
+ final Class<C> contextClass) {
+
+ // Populate interceptors.
+ final Map<String, PluginType<?>> pluginTypeByName =
+ PluginUtil.collectPluginsByCategoryAndPackage(
+ TemplateResolverInterceptor.CATEGORY,
+ pluginPackages);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(
+ "found {} plugins of category \"{}\": {}",
+ pluginTypeByName.size(),
+ TemplateResolverFactory.CATEGORY,
+ pluginTypeByName.keySet());
+ }
+
+ // Filter matching interceptors.
+ final List<I> interceptors =
+ populateInterceptors(pluginTypeByName, valueClass, contextClass);
+ LOGGER.debug(
+ "{} interceptors matched out of {} for value class {} and context class {}",
+ interceptors.size(),
+ pluginTypeByName.size(),
+ valueClass,
+ contextClass);
+ return interceptors;
+
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>, I extends TemplateResolverInterceptor<V, C>> List<I> populateInterceptors(
+ final Map<String, PluginType<?>> pluginTypeByName,
+ final Class<V> valueClass,
+ final Class<C> contextClass) {
+ final List<I> interceptors = new LinkedList<>();
+ final Set<String> pluginNames = pluginTypeByName.keySet();
+ for (final String pluginName : pluginNames) {
+ final PluginType<?> pluginType = pluginTypeByName.get(pluginName);
+ final Class<?> pluginClass = pluginType.getPluginClass();
+ final boolean pluginClassMatched =
+ TemplateResolverInterceptor.class.isAssignableFrom(pluginClass);
+ if (pluginClassMatched) {
+ final TemplateResolverInterceptor<?, ?> rawInterceptor =
+ instantiateInterceptor(pluginName, pluginClass);
+ final I interceptor =
+ castInterceptor(valueClass, contextClass, rawInterceptor);
+ if (interceptor != null) {
+ interceptors.add(interceptor);
+ }
+ }
+ }
+ return interceptors;
+ }
+
+ private static TemplateResolverInterceptor<?, ?> instantiateInterceptor(
+ final String pluginName,
+ final Class<?> pluginClass) {
+ try {
+ return (TemplateResolverInterceptor<?, ?>)
+ PluginUtil.instantiatePlugin(pluginClass);
+ } catch (final Exception error) {
+ final String message = String.format(
+ "failed instantiating resolver interceptor plugin %s of name %s",
+ pluginClass, pluginName);
+ throw new RuntimeException(message, error);
+ }
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>, I extends TemplateResolverInterceptor<V, C>> I castInterceptor(
+ final Class<V> valueClass,
+ final Class<C> contextClass,
+ final TemplateResolverInterceptor<?, ?> interceptor) {
+ final Class<?> interceptorValueClass = interceptor.getValueClass();
+ final Class<?> interceptorContextClass = interceptor.getContextClass();
+ final boolean interceptorValueClassMatched =
+ valueClass.isAssignableFrom(interceptorValueClass);
+ final boolean interceptorContextClassMatched =
+ contextClass.isAssignableFrom(interceptorContextClass);
+ if (interceptorValueClassMatched && interceptorContextClassMatched) {
+ @SuppressWarnings({"unchecked"})
+ final I typedInterceptor = (I) interceptor;
+ return typedInterceptor;
+ }
+ return null;
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverStringSubstitutor.java
similarity index 67%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverStringSubstitutor.java
index fbb0da4..16a20b4 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverContext.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverStringSubstitutor.java
@@ -17,18 +17,22 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import java.util.Map;
-
-interface TemplateResolverContext<V, C extends TemplateResolverContext<V, C>> {
-
- Class<C> getContextClass();
+/**
+ * A contextual {@link StrSubstitutor} abstraction.
+ *
+ * @param <V> {@link TemplateResolver} value
+ */
+public interface TemplateResolverStringSubstitutor<V> {
- Map<String, TemplateResolverFactory<V, C, ? extends TemplateResolver<V>>> getResolverFactoryByName();
+ StrSubstitutor getInternalSubstitutor();
- StrSubstitutor getSubstitutor();
+ /**
+ * A substitutor is stable if the replacement doesn't vary with the provided
+ * value. In such a case, value is always set to {@code null}.
+ */
+ boolean isStable();
- JsonWriter getJsonWriter();
+ String replace(V value, String source);
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
index b0cede2..27a5b49 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
@@ -16,17 +16,17 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
import org.apache.logging.log4j.layout.template.json.util.JsonReader;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+/**
+ * Main class for compiling {@link TemplateResolver}s from a template.
+ */
public final class TemplateResolvers {
private TemplateResolvers() {}
@@ -87,19 +87,16 @@ public final class TemplateResolvers {
throw new RuntimeException(message, error);
}
- if (context instanceof EventResolverContext) {
-
- // Append the additional fields.
- final EventResolverContext eventResolverContext = (EventResolverContext) context;
- final EventTemplateAdditionalField[] additionalFields = eventResolverContext.getAdditionalFields();
- appendAdditionalFields(node, additionalFields);
-
- // Set the root object key, if given.
- final String rootObjectKey = eventResolverContext.getEventTemplateRootObjectKey();
- if (rootObjectKey != null) {
- node = Collections.singletonMap(rootObjectKey, node);
- }
-
+ // Perform contextual interception.
+ final List<? extends TemplateResolverInterceptor<V, C>> interceptors =
+ context.getResolverInterceptors();
+ // noinspection ForLoopReplaceableByForEach
+ for (int interceptorIndex = 0;
+ interceptorIndex < interceptors.size();
+ interceptorIndex++) {
+ final TemplateResolverInterceptor<V, C> interceptor =
+ interceptors.get(interceptorIndex);
+ node = interceptor.processTemplateBeforeResolverInjection(context, node);
}
// Resolve the template.
@@ -107,55 +104,6 @@ public final class TemplateResolvers {
}
- private static void appendAdditionalFields(
- final Object node,
- EventTemplateAdditionalField[] additionalFields) {
- if (additionalFields.length > 0) {
-
- // Check that the root is an object node.
- final Map<String, Object> objectNode;
- try {
- @SuppressWarnings("unchecked")
- final Map<String, Object> map = (Map<String, Object>) node;
- objectNode = map;
- } catch (final ClassCastException error) {
- final String message = String.format(
- "was expecting an object to merge additional fields: %s",
- node.getClass().getName());
- throw new IllegalArgumentException(message);
- }
-
- // Merge additional fields.
- for (final EventTemplateAdditionalField additionalField : additionalFields) {
- final String additionalFieldKey = additionalField.getKey();
- final Object additionalFieldValue;
- switch (additionalField.getFormat()) {
- case STRING:
- additionalFieldValue = additionalField.getValue();
- break;
- case JSON:
- try {
- additionalFieldValue = JsonReader.read(additionalField.getValue());
- } catch (final Exception error) {
- final String message = String.format(
- "failed reading JSON provided by additional field: %s",
- additionalFieldKey);
- throw new IllegalArgumentException(message, error);
- }
- break;
- default: {
- final String message = String.format(
- "unknown format %s for additional field: %s",
- additionalFieldKey, additionalField.getFormat());
- throw new IllegalArgumentException(message);
- }
- }
- objectNode.put(additionalFieldKey, additionalFieldValue);
- }
-
- }
- }
-
private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofObject(
final C context,
final Object object) {
@@ -352,7 +300,7 @@ public final class TemplateResolvers {
final String resolverName = (String) resolverNameObject;
// Retrieve the resolver.
- final TemplateResolverFactory<V, C, ? extends TemplateResolver<V>> resolverFactory =
+ final TemplateResolverFactory<V, C> resolverFactory =
context.getResolverFactoryByName().get(resolverName);
if (resolverFactory == null) {
throw new IllegalArgumentException("unknown resolver: " + resolverName);
@@ -366,24 +314,16 @@ public final class TemplateResolvers {
final C context,
final String fieldValue) {
- // Check if substitution needed at all. (Copied logic from
- // AbstractJacksonLayout.valueNeedsLookup() method.)
+ // Check if substitution is needed.
final boolean substitutionNeeded = fieldValue.contains("${");
final JsonWriter contextJsonWriter = context.getJsonWriter();
if (substitutionNeeded) {
+ final TemplateResolverStringSubstitutor<V> substitutor = context.getSubstitutor();
- // Use Log4j substitutor with LogEvent.
- if (EventResolverContext.class.isAssignableFrom(context.getContextClass())) {
- return (final V value, final JsonWriter jsonWriter) -> {
- final LogEvent logEvent = (LogEvent) value;
- final String replacedText = context.getSubstitutor().replace(logEvent, fieldValue);
- jsonWriter.writeString(replacedText);
- };
- }
-
- // Use standalone Log4j substitutor.
- else {
- final String replacedText = context.getSubstitutor().replace(null, fieldValue);
+ // If the substitutor is stable, we can get the replacement right
+ // away and avoid runtime substitution.
+ if (substitutor.isStable()) {
+ final String replacedText = substitutor.replace(null, fieldValue);
if (replacedText == null) {
@SuppressWarnings("unchecked")
final TemplateResolver<V> resolver =
@@ -400,6 +340,15 @@ public final class TemplateResolvers {
}
}
+ // Otherwise, the unstable substitutor needs to be invoked always at
+ // runtime.
+ else {
+ return (final V value, final JsonWriter jsonWriter) -> {
+ final String replacedText = substitutor.replace(value, fieldValue);
+ jsonWriter.writeString(replacedText);
+ };
+ }
+
}
// Write the field value as is.
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
index 95e3677..1875e1f 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.core.LogEvent;
*
* @see ReadOnlyStringMapResolver
*/
-final class ThreadContextDataResolver extends ReadOnlyStringMapResolver {
+public final class ThreadContextDataResolver extends ReadOnlyStringMapResolver {
ThreadContextDataResolver(
final EventResolverContext context,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolverFactory.java
index 05474a3..0cb2c07 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolverFactory.java
@@ -16,15 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class ThreadContextDataResolverFactory
- implements EventResolverFactory<ThreadContextDataResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link ThreadContextDataResolver} factory.
+ */
+@Plugin(name = "ThreadContextDataResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class ThreadContextDataResolverFactory implements EventResolverFactory {
private static final ThreadContextDataResolverFactory INSTANCE =
new ThreadContextDataResolverFactory();
private ThreadContextDataResolverFactory() {}
- static ThreadContextDataResolverFactory getInstance() {
+ @PluginFactory
+ public static ThreadContextDataResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolver.java
index 4f4afa3..7a70e47 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolver.java
@@ -52,7 +52,7 @@ import java.util.regex.Pattern;
* }
* </pre>
*/
-final class ThreadContextStackResolver implements EventResolver {
+public final class ThreadContextStackResolver implements EventResolver {
private final Pattern itemPattern;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolverFactory.java
index 4946f0d..ad22bb9 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextStackResolverFactory.java
@@ -16,15 +16,22 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class ThreadContextStackResolverFactory
- implements EventResolverFactory<ThreadContextStackResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
- private static final ThreadContextStackResolverFactory INSTANCE
- = new ThreadContextStackResolverFactory();
+/**
+ * {@link ThreadContextStackResolver} factory.
+ */
+@Plugin(name = "ThreadContextStackResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class ThreadContextStackResolverFactory implements EventResolverFactory {
+
+ private static final ThreadContextStackResolverFactory INSTANCE =
+ new ThreadContextStackResolverFactory();
private ThreadContextStackResolverFactory() {}
- static ThreadContextStackResolverFactory getInstance() {
+ @PluginFactory
+ public static ThreadContextStackResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolver.java
index 4ea7d80..eb1e050 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolver.java
@@ -39,7 +39,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
* }
* </pre>
*/
-final class ThreadResolver implements EventResolver {
+public final class ThreadResolver implements EventResolver {
private static final EventResolver NAME_RESOLVER =
(final LogEvent logEvent, final JsonWriter jsonWriter) -> {
@@ -68,10 +68,12 @@ final class ThreadResolver implements EventResolver {
private static EventResolver createInternalResolver(
final TemplateResolverConfig config) {
final String fieldName = config.getString("field");
- switch (fieldName) {
- case "name": return NAME_RESOLVER;
- case "id": return ID_RESOLVER;
- case "priority": return PRIORITY_RESOLVER;
+ if ("name".equals(fieldName)) {
+ return NAME_RESOLVER;
+ } else if ("id".equals(fieldName)) {
+ return ID_RESOLVER;
+ } else if ("priority".equals(fieldName)) {
+ return PRIORITY_RESOLVER;
}
throw new IllegalArgumentException("unknown field: " + config);
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolverFactory.java
index 69d5e91..ec65c56 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class ThreadResolverFactory implements EventResolverFactory<ThreadResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link ThreadResolver} factory.
+ */
+@Plugin(name = "ThreadResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class ThreadResolverFactory implements EventResolverFactory {
private static final ThreadResolverFactory INSTANCE = new ThreadResolverFactory();
private ThreadResolverFactory() {}
- static ThreadResolverFactory getInstance() {
+ @PluginFactory
+ public static ThreadResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
index eea0dd6..9488f0d 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
@@ -191,7 +191,7 @@ import java.util.TimeZone;
* </tr>
* </table>
*/
-final class TimestampResolver implements EventResolver {
+public final class TimestampResolver implements EventResolver {
private final EventResolver internalResolver;
@@ -381,7 +381,7 @@ final class TimestampResolver implements EventResolver {
private Instant instant;
- private char[] resolution = new char[
+ private final char[] resolution = new char[
/* integral: */ MAX_LONG_LENGTH +
/* dot: */ 1 +
/* fractional: */ MAX_LONG_LENGTH];
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolverFactory.java
index 6f4cb6d..054785b 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolverFactory.java
@@ -16,13 +16,21 @@
*/
package org.apache.logging.log4j.layout.template.json.resolver;
-final class TimestampResolverFactory implements EventResolverFactory<TimestampResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link TimestampResolver} factory.
+ */
+@Plugin(name = "TimestampResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class TimestampResolverFactory implements EventResolverFactory {
private static final TimestampResolverFactory INSTANCE = new TimestampResolverFactory();
private TimestampResolverFactory() {}
- static TimestampResolverFactory getInstance() {
+ @PluginFactory
+ public static TimestampResolverFactory getInstance() {
return INSTANCE;
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java
index b839619..22244fb 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactories.java
@@ -16,9 +16,6 @@
*/
package org.apache.logging.log4j.layout.template.json.util;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.util.LoaderUtil;
import org.jctools.queues.MpmcArrayQueue;
@@ -53,14 +50,6 @@ public final class RecyclerFactories {
}
}
- @Plugin(name = "RecyclerFactory", category = TypeConverters.CATEGORY)
- public static final class RecyclerFactoryConverter implements TypeConverter<RecyclerFactory> {
- @Override
- public RecyclerFactory convert(final String recyclerFactorySpec) {
- return ofSpec(recyclerFactorySpec);
- }
- }
-
public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
// Determine the default capacity.
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
similarity index 55%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
index 08b6fb9..3111af5 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/LoggerResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
@@ -14,28 +14,21 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-package org.apache.logging.log4j.layout.template.json.resolver;
+package org.apache.logging.log4j.layout.template.json.util;
-final class LoggerResolverFactory implements EventResolverFactory<LoggerResolver> {
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
- private static final LoggerResolverFactory INSTANCE = new LoggerResolverFactory();
-
- private LoggerResolverFactory() {}
-
- static LoggerResolverFactory getInstance() {
- return INSTANCE;
- }
-
- @Override
- public String getName() {
- return LoggerResolver.getName();
- }
+/**
+ * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter.
+ */
+@Plugin(name = "RecyclerFactoryConverter", category = TypeConverters.CATEGORY)
+public final class RecyclerFactoryConverter implements TypeConverter<RecyclerFactory> {
@Override
- public LoggerResolver create(
- final EventResolverContext context,
- final TemplateResolverConfig config) {
- return new LoggerResolver(config);
+ public RecyclerFactory convert(final String recyclerFactorySpec) {
+ return RecyclerFactories.ofSpec(recyclerFactorySpec);
}
}
diff --git a/log4j-layout-template-json/src/main/resources/EcsLayout.json b/log4j-layout-template-json/src/main/resources/EcsLayout.json
index 708b27b..8d215ab 100644
--- a/log4j-layout-template-json/src/main/resources/EcsLayout.json
+++ b/log4j-layout-template-json/src/main/resources/EcsLayout.json
@@ -6,6 +6,7 @@
"timeZone": "UTC"
}
},
+ "ecs.version": "1.2.0",
"log.level": {
"$resolver": "level",
"field": "name"
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
index 9f806f5..f58e3c4 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
@@ -24,6 +24,8 @@ import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTem
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -34,6 +36,8 @@ class EcsLayoutTest {
private static final Configuration CONFIGURATION = new DefaultConfiguration();
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
private static final String SERVICE_NAME = "test";
private static final String EVENT_DATASET = SERVICE_NAME + ".log";
@@ -41,6 +45,7 @@ class EcsLayoutTest {
private static final JsonTemplateLayout JSON_TEMPLATE_LAYOUT = JsonTemplateLayout
.newBuilder()
.setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
.setEventTemplateUri("classpath:EcsLayout.json")
.setEventTemplateAdditionalFields(
new EventTemplateAdditionalField[]{
@@ -65,6 +70,11 @@ class EcsLayoutTest {
.build();
@Test
+ void test_EcsLayout_charset() {
+ Assertions.assertThat(ECS_LAYOUT.getCharset()).isEqualTo(CHARSET);
+ }
+
+ @Test
void test_lite_log_events() {
final List<LogEvent> logEvents = LogEventFixture.createLiteLogEvents(1_000);
test(logEvents);
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
index 6c81991..d75e0ab 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
@@ -28,12 +28,20 @@ import org.apache.logging.log4j.core.appender.SocketAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.lookup.MainMapLookup;
import org.apache.logging.log4j.core.net.Severity;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolver;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory;
import org.apache.logging.log4j.layout.template.json.util.JsonReader;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.layout.template.json.util.MapAccessor;
@@ -73,6 +81,7 @@ import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -1794,6 +1803,115 @@ class JsonTemplateLayoutTest {
}
@Test
+ void test_inline_stack_trace_element_template() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(asMap(
+ "stackTrace", asMap(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stackTrace", asMap(
+ "elementTemplate", asMap(
+ "$resolver", "stackTraceElement",
+ "field", "className")))));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event.
+ final Throwable error = new RuntimeException("foo");
+ final SimpleMessage message = new SimpleMessage("foo");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setThrown(error)
+ .build();
+
+ // Check the serialized log event.
+ final String expectedClassName = JsonTemplateLayoutTest.class.getCanonicalName();
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> Assertions
+ .assertThat(accessor.getList("stackTrace", String.class))
+ .contains(expectedClassName));
+
+ }
+
+ @Test
+ void test_custom_resolver() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(asMap(
+ "customField", asMap("$resolver", "custom")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("foo");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .build();
+
+ // Check the serialized log event.
+ final String expectedClassName = JsonTemplateLayoutTest.class.getCanonicalName();
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> Assertions
+ .assertThat(accessor.getString("customField"))
+ .matches("CustomValue-[0-9]+"));
+
+ }
+
+ private static final class CustomResolver implements EventResolver {
+
+ private static final AtomicInteger COUNTER = new AtomicInteger(0);
+
+ private CustomResolver() {}
+
+ @Override
+ public void resolve(
+ final LogEvent value,
+ final JsonWriter jsonWriter) {
+ jsonWriter.writeString("CustomValue-" + COUNTER.getAndIncrement());
+ }
+
+ }
+
+ @Plugin(name = "CustomResolverFactory", category = TemplateResolverFactory.CATEGORY)
+ public static final class CustomResolverFactory implements EventResolverFactory {
+
+ private static final CustomResolverFactory INSTANCE = new CustomResolverFactory();
+
+ private CustomResolverFactory() {}
+
+ @PluginFactory
+ public static CustomResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return "custom";
+ }
+
+ @Override
+ public TemplateResolver<LogEvent> create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new CustomResolver();
+ }
+
+ }
+
+ @Test
void test_null_eventDelimiter() {
// Create the event template.
@@ -2291,9 +2409,9 @@ class JsonTemplateLayoutTest {
final Consumer<MapAccessor> accessorConsumer) {
final String serializedLogEventJson = layout.toSerializable(logEvent);
@SuppressWarnings("unchecked")
- final Map<String, Object> serializedLogEvent =
+ final Map<String, Object> deserializedLogEvent =
(Map<String, Object>) readJson(serializedLogEventJson);
- final MapAccessor serializedLogEventAccessor = new MapAccessor(serializedLogEvent);
+ final MapAccessor serializedLogEventAccessor = new MapAccessor(deserializedLogEvent);
accessorConsumer.accept(serializedLogEventAccessor);
}
@@ -2304,7 +2422,7 @@ class JsonTemplateLayoutTest {
private static Map<String, Object> asMap(final Object... pairs) {
final Map<String, Object> map = new LinkedHashMap<>();
if (pairs.length % 2 != 0) {
- throw new IllegalArgumentException("odd number of arguments");
+ throw new IllegalArgumentException("odd number of arguments: " + pairs.length);
}
for (int i = 0; i < pairs.length; i += 2) {
final String key = (String) pairs[i];
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
index f38b823..4b64bec 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
@@ -109,10 +109,11 @@ class LogstashIT {
})
.build();
+ // Note that EcsLayout doesn't support charset configuration, though it uses
+ // UTF-8 internally.
private static final EcsLayout ECS_LAYOUT = EcsLayout
.newBuilder()
.setConfiguration(CONFIGURATION)
- .setCharset(CHARSET)
.setServiceName(SERVICE_NAME)
.setEventDataset(EVENT_DATASET)
.build();
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
index 90dd89d..9a46528 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
@@ -145,12 +145,19 @@ public class JsonTemplateLayoutBenchmarkState {
}
private static EcsLayout createEcsLayout() {
- return EcsLayout
+ final EcsLayout layout = EcsLayout
.newBuilder()
.setConfiguration(CONFIGURATION)
- .setCharset(CHARSET)
.setServiceName("benchmark")
.build();
+ final Charset layoutCharset = layout.getCharset();
+ // Note that EcsLayout doesn't support charset configuration, though it
+ // uses UTF-8 internally.
+ if (CHARSET.equals(layoutCharset)) {
+ throw new IllegalArgumentException(
+ "invalid EcsLayout charset: " + layoutCharset);
+ }
+ return layout;
}
private static GelfLayout createGelfLayout() {
diff --git a/pom.xml b/pom.xml
index a46a550..4869e2f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -873,7 +873,7 @@
<dependency>
<groupId>co.elastic.logging</groupId>
<artifactId>log4j2-ecs-layout</artifactId>
- <version>0.5.2</version>
+ <version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 088c844..3218bb0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,10 @@
- "remove" - Removed
-->
<release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
+ <!-- ADDS -->
+ <action issue="LOG4J2-3004" dev="vy" type="add">
+ Add plugin support to JsonTemplateLayout.
+ </action>
<action issue="LOG4J2-3050" dev="rgoers" type="add">
Allow AdditionalFields to be ignored if their value is null or a zero-length String.
</action>
@@ -42,6 +46,7 @@
<action issue="LOG4J2-3044" dev="rgoers" type="add">
Add RepeatPatternConverter.
</action>
+ <!-- UPDATES -->
<action issue="LOG4J2-3041" dev="rgoers" type="update">
Allow a PatternSelector to be specified on GelfLayout.
</action>
diff --git a/src/site/asciidoc/manual/json-template-layout.vm.adoc b/src/site/asciidoc/manual/json-template-layout.adoc.vm
similarity index 65%
rename from src/site/asciidoc/manual/json-template-layout.vm.adoc
rename to src/site/asciidoc/manual/json-template-layout.adoc.vm
index c0ff96b..c3a9e05 100644
--- a/src/site/asciidoc/manual/json-template-layout.vm.adoc
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -19,13 +19,21 @@
Volkan Yazıcı <vy...@apache.org>
`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON
-emitting layout. It encodes ``LogEvent``s according to the structure described
+generating layout. It encodes ``LogEvent``s according to the structure described
by the JSON template provided. In a nutshell, it shines with its
* Customizable JSON structure (see `eventTemplate[Uri]` and
- `stackTraceElementTemplate[Uri]` parameters)
+ `stackTraceElementTemplate[Uri]` xref:layout-config[layout configuration] parameters)
-* Customizable timestamp formatting (see `timestamp` parameter)
+* Customizable timestamp formatting (see xref:event-template-resolver-timestamp[]
+ event template resolver)
+
+* Feature rich exception formatting (see xref:event-template-resolver-exception[]
+ and xref:event-template-resolver-exceptionRootCause[] event template resolvers)
+
+* xref:extending[Extensible plugin support]
+
+* Customizable object xref:recycling-strategy[recycling strategy]
[#usage]
== Usage
@@ -43,68 +51,58 @@ enough to enable access to `JsonTemplateLayout` in your Log4j configuration:
----
For instance, given the following JSON template modelling
-https://github.com/logstash/log4j-jsonevent-layout[the official Logstash
-`JSONEventLayoutV1`] (accessible via `classpath:LogstashJsonEventLayoutV1.json`)
+https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification]
+(accessible via `classpath:EcsLayout.json`)
[source,json]
----
{
- "mdc": {
- "$resolver": "mdc"
- },
- "exception": {
- "exception_class": {
- "$resolver": "exception",
- "field": "className"
- },
- "exception_message": {
- "$resolver": "exception",
- "field": "message"
- },
- "stacktrace": {
- "$resolver": "exception",
- "field": "stackTrace",
- "stackTrace": {
- "stringified": true
- }
+ "@timestamp": {
+ "$resolver": "timestamp",
+ "pattern": {
+ "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "timeZone": "UTC"
}
},
- "line_number": {
- "$resolver": "source",
- "field": "lineNumber"
- },
- "class": {
- "$resolver": "source",
- "field": "className"
+ "ecs.version": "1.2.0",
+ "log.level": {
+ "$resolver": "level",
+ "field": "name"
},
- "@version": 1,
- "source_host": "${hostName}",
"message": {
"$resolver": "message",
"stringified": true
},
- "thread_name": {
+ "process.thread.name": {
"$resolver": "thread",
"field": "name"
},
- "@timestamp": {
- "$resolver": "timestamp"
- },
- "level": {
- "$resolver": "level",
+ "log.logger": {
+ "$resolver": "logger",
"field": "name"
},
- "file": {
- "$resolver": "source",
- "field": "fileName"
+ "labels": {
+ "$resolver": "mdc",
+ "flatten": true,
+ "stringified": true
},
- "method": {
- "$resolver": "source",
- "field": "methodName"
+ "tags": {
+ "$resolver": "ndc"
},
- "logger_name": {
- "$resolver": "logger",
- "field": "name"
+ "error.type": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "error.message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "error.stack_trace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stackTrace": {
+ "stringified": true
+ }
}
}
----
@@ -113,7 +111,7 @@ in combination with the below `log4j2.xml` configuration:
[source,xml]
----
-<JsonTemplateLayout eventTemplateUri="classpath:LogstashJsonEventLayoutV1.json"/>
+<JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
----
or with the below `log4j2.properties` configuration:
@@ -121,30 +119,23 @@ or with the below `log4j2.properties` configuration:
[source,ini]
----
appender.console.json.type = JsonTemplateLayout
-appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.json
+appender.console.json.eventTemplateUri = classpath:EcsLayout.json
----
-`JsonTemplateLayout` emits JSON strings as follows:
+`JsonTemplateLayout` generates JSON as follows:
[source,json]
----
{
- "exception": {
- "exception_class": "java.lang.RuntimeException",
- "exception_message": "test",
- "stacktrace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
- },
- "line_number": 12,
- "class": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
- "@version": 1,
- "source_host": "varlik",
+ "@timestamp": "2017-05-25T19:56:23.370Z",
+ "ecs.version": "1.2.0",
+ "log.level": "ERROR",
"message": "Hello, error!",
- "thread_name": "main",
- "@timestamp": "2017-05-25T19:56:23.370+02:00",
- "level": "ERROR",
- "file": "JsonTemplateLayoutDemo.java",
- "method": "main",
- "logger_name": "org.apache.logging.log4j.JsonTemplateLayoutDemo"
+ "process.thread.name": "main",
+ "log.logger": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
+ "error.type": "java.lang.RuntimeException",
+ "error.message": "test",
+ "error.stack_trace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
}
----
@@ -189,12 +180,12 @@ appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.jso
| eventTemplateRootObjectKey
| String
-| if given, puts the event template into a JSON object composed of a single
- member with the given key (defaults to `null` set by
+| if present, the event template is put into a JSON object composed of a single
+ member with the provided key (defaults to `null` set by
`log4j.layout.jsonTemplate.eventTemplateRootObjectKey`
property)
-| eventTemplateAdditionalFields
+| eventTemplateAdditionalField
| EventTemplateAdditionalField[]
| additional key-value pairs appended to the root of the event template
@@ -206,20 +197,20 @@ appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.jso
| stackTraceElementTemplateUri
| String
-| JSON template for rendering ``StackTraceElement``s (defaults to
- `classpath:StackTraceElementLayout.json` set by
+| URI pointing to the JSON template for rendering ``StackTraceElement``s
+ (defaults to `classpath:StackTraceElementLayout.json` set by
`log4j.layout.jsonTemplate.stackTraceElementTemplateUri` property)
| eventDelimiter
| String
-| delimiter used for separating emitted ``LogEvent``s (defaults to
+| delimiter used for separating rendered ``LogEvent``s (defaults to
`System.lineSeparator()` set by `log4j.layout.jsonTemplate.eventDelimiter`
property)
| nullEventDelimiterEnabled
| boolean
-| append `\0` (`null`) character to the end of every emitted `eventDelimiter`
- (defaults to `false` set by
+| append `\0` (`null`) character to the end of every `eventDelimiter`
+ separating rendered ``LogEvent``s (defaults to `false` set by
`log4j.layout.jsonTemplate.nullEventDelimiterEnabled` property)
| maxStringLength
@@ -240,7 +231,7 @@ appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.jso
|===
[#additional-event-template-fields]
-=== Additonal event template fields
+=== Additional event template fields
Additional event template fields are a convenient short-cut to add custom fields
to a template or override the existing ones. Following configuration overrides
@@ -348,7 +339,7 @@ JsonTemplateLayout:
`RecyclerFactory` plays a crucial role for determining the memory footprint of
the layout. Template resolvers employ it to create recyclers for objects that
-they can reuse. The function of each `RecyclerFactory` and when one should
+they can reuse. The behavior of each `RecyclerFactory` and when one should
prefer one over another is explained below:
* `dummy` performs no recycling, hence each recycling attempt will result in a
@@ -371,6 +362,7 @@ trying to log), it starts allocating. `queue` is a good strategy where
otherwise `java.util.concurrent.ArrayBlockingQueue.new`) and `capacity` (of
type `int`, defaults to `max(8,2*cpuCount+1)`) parameters:
+
+.Example configurations of `queue` recycling strategy
[source]
----
queue:supplier=org.jctools.queues.MpmcArrayQueue.new
@@ -381,6 +373,9 @@ queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=50
The default `RecyclerFactory` is `threadLocal`, if
`log4j2.enable.threadlocals=true`; otherwise, `queue`.
+See <<extending-recycler>> for details on how to introduce custom
+`RecyclerFactory` implementations.
+
[#template-config]
== Template Configuration
@@ -389,7 +384,7 @@ parameters:
- `eventTemplate[Uri]` (for serializing ``LogEvent``s)
- `stackTraceElementTemplate[Uri]` (for serializing ``StackStraceElement``s)
-- `eventTemplateAdditionalFields` (for extending the used event template)
+- `eventTemplateAdditionalField` (for extending the used event template)
[#event-templates]
=== Event Templates
@@ -398,63 +393,7 @@ parameters:
serialize ``LogEvent``s. The default configuration (accessible by
`log4j.layout.jsonTemplate.eventTemplate[Uri]` property) is set to
`classpath:EcsLayout.json` provided by the `log4j-layout-template-json`
-artifact:
-
-[source,json]
-----
-{
- "@timestamp": {
- "$resolver": "timestamp",
- "pattern": {
- "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
- "timeZone": "UTC"
- }
- },
- "log.level": {
- "$resolver": "level",
- "field": "name"
- },
- "message": {
- "$resolver": "message",
- "stringified": true
- },
- "process.thread.name": {
- "$resolver": "thread",
- "field": "name"
- },
- "log.logger": {
- "$resolver": "logger",
- "field": "name"
- },
- "labels": {
- "$resolver": "mdc",
- "flatten": true,
- "stringified": true
- },
- "tags": {
- "$resolver": "ndc"
- },
- "error.type": {
- "$resolver": "exception",
- "field": "className"
- },
- "error.message": {
- "$resolver": "exception",
- "field": "message"
- },
- "error.stack_trace": {
- "$resolver": "exception",
- "field": "stackTrace",
- "stackTrace": {
- "stringified": true
- }
- }
-}
-
-----
-
-`log4j-layout-template-json` artifact contains the following predefined event
-templates:
+artifact, which contains the following predefined event templates:
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/EcsLayout.json[`EcsLayout.json`]
described by https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification]
@@ -467,7 +406,8 @@ templates:
described by https://docs.graylog.org/en/3.1/pages/gelf.html#gelf-payload-specification[the
Graylog Extended Log Format (GELF) payload specification] with additional
`_thread` and `_logger` fields. (Here it is advised to override the obligatory
- `host` field with a user provided constant via `eventTemplateAdditionalFields`
+ `host` field with a user provided constant via
+ xref:additional-event-template-fields[additional event template fields]
to avoid `hostName` property lookup at runtime, which incurs an extra cost.)
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/JsonLayout.json[`JsonLayout.json`]
@@ -480,6 +420,40 @@ templates:
[#event-template-resolvers]
==== Event Template Resolvers
+Event template resolvers consume a `LogEvent` and render a certain property of
+it at the point of the JSON where they are declared. For instance, `marker`
+resolver renders the marker of the event, `level` resolver renders the level,
+and so on. An event template resolver is denoted with a special object
+containing a`${dollar}resolver` key:
+
+.Example event template demonstrating the usage of `level` resolver
+[source,json]
+----
+{
+ "version": "1.0",
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ }
+}
+----
+
+Here `version` field will be rendered as is, while `level` field will be
+populated by the `level` resolver. That is, this template will generate JSON
+similar to the following:
+
+.Example JSON generated from the demonstrated event template
+[source,json]
+----
+{
+ "version": "1.0",
+ "level": "INFO"
+}
+----
+
+The complete list of available event template resolvers are provided below in
+detail.
+
[#event-template-resolver-endOfBatch]
===== `endOfBatch`
@@ -498,10 +472,14 @@ Resolves `logEvent.isEndOfBatch()` boolean flag:
[source]
----
config = field , [ stringified ] , [ stackTrace ]
-field = "field" -> ( "className" \| "message" \| "stackTrace" )
+field = "field" -> ( "className" | "message" | "stackTrace" )
+
+stackTrace = "stackTrace" -> (
+ [ stringified ]
+ , [ elementTemplate ]
+ )
-stackTrace = "stackTrace" -> stringified
-stringified = "stringified" -> ( boolean \| truncation )
+stringified = "stringified" -> ( boolean | truncation )
truncation = "truncation" -> (
[ suffix ]
, [ pointMatcherStrings ]
@@ -510,6 +488,8 @@ truncation = "truncation" -> (
suffix = "suffix" -> string
pointMatcherStrings = "pointMatcherStrings" -> string[]
pointMatcherRegexes = "pointMatcherRegexes" -> string[]
+
+elementTemplate = "elementTemplate" -> object
----
Resolves fields of the `Throwable` returned by `logEvent.getThrown()`.
@@ -526,6 +506,27 @@ If a stringified stack trace truncation takes place, it will be indicated with
`suffix`, which by default is set to the configured `truncatedStringSuffix` in
the layout, unless explicitly provided.
+`elementTemplate` is an object describing the template to be used while
+resolving the `StackTraceElement` array. If `stringified` is set to `true`,
+`elementTemplate` will be discarded. By default, `elementTemplate` is set to
+`null` and rather populated from the layout configuration. That is, the stack
+trace element template can also be provided using
+`stackTraceElementTemplate[Uri]` layout configuration parameters. The template
+to be employed is determined in the following order:
+
+. `elementTemplate` provided in the resolver configuration
+
+. `stackTraceElementTemplate` parameter from layout configuration
+(the default is populated from `log4j.layout.jsonTemplate.stackTraceElementTemplate`
+system property)
+
+. `stackTraceElementTemplateUri` parameter from layout configuration
+(the default is populated from `log4j.layout.jsonTemplate.stackTraceElementTemplateUri`
+system property)
+
+See <<stack-trace-element-templates>>
+for the list of available resolvers in a stack trace element template.
+
Note that this resolver is toggled by
`log4j.layout.jsonTemplate.stackTraceEnabled` property.
@@ -572,7 +573,7 @@ Resolve the stack trace into a string field:
----
Resolve the stack trace into a string field such that the content will be
-truncated by the given point matcher:
+truncated after the given point matcher:
[source,json]
----
@@ -590,12 +591,46 @@ truncated by the given point matcher:
}
----
+Resolve the stack trace into an object described by the provided stack trace
+element template:
+
+[source,json]
+----
+{
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stackTrace": {
+ "elementTemplate": {
+ "class": {
+ "$resolver": "stackTraceElement",
+ "field": "className"
+ },
+ "method": {
+ "$resolver": "stackTraceElement",
+ "field": "methodName"
+ },
+ "file": {
+ "$resolver": "stackTraceElement",
+ "field": "fileName"
+ },
+ "line": {
+ "$resolver": "stackTraceElement",
+ "field": "lineNumber"
+ }
+ }
+ }
+}
+----
+
+See <<stack-trace-element-templates>> for further details on resolvers available
+for ``StackTraceElement`` templates.
+
[#event-template-resolver-exceptionRootCause]
===== `exceptionRootCause`
Resolves the fields of the innermost `Throwable` returned by
`logEvent.getThrown()`. Its syntax and garbage-footprint are identical to the
-link:#event-template-exception[`exception`] resolver.
+xref:event-template-resolver-exception[] resolver.
[#event-template-resolver-level]
===== `level`
@@ -1206,6 +1241,12 @@ parent such that keys are prefixed with `_`:
[#stack-trace-element-templates]
=== Stack Trace Element Templates
+xref:event-template-resolver-exception[] and
+xref:event-template-resolver-exceptionRootCause[] event template resolvers can
+serialize an exception stack trace (i.e., `StackTraceElement[]` returned by
+`Throwable#getStackTrace()`) into a JSON array. While doing so, JSON templating
+infrastructure is used again.
+
`stackTraceElement[Uri]` describes the JSON structure `JsonTemplateLayout` uses
to format ``StackTraceElement``s. The default configuration (accessible by
`log4j.layout.jsonTemplate.stackTraceElementTemplate[Uri]` property) is set to
@@ -1247,6 +1288,291 @@ config = "field" -> (
All above accesses to `StackTraceElement` is garbage-free.
+[#extending]
+== Extending
+
+`JsonTemplateLayout` relies on Log4j link:plugins.html[plugin system] to build
+up the features it provides. This enables feature customization a breeze for
+users. As of this moment, following features are implemented by means of
+plugins:
+
+* Event template resolvers (e.g., `exception`, `message`, `level` event template resolvers)
+* Event template interceptors (e.g., injection of `eventTemplateAdditionalField`)
+* Recycler factories
+
+Following sections cover these in detail.
+
+[#extending-plugins]
+=== Plugin Preliminaries
+
+Log4j plugin system is the de facto extension mechanism embraced by various
+Log4j components, including `JsonTemplateLayout`. Plugins make it possible
+for extensible components _receive_ feature implementations without any explicit
+links in between. It is analogous to a
+https://en.wikipedia.org/wiki/Dependency_injection[dependency injection]
+framework, but curated for Log4j-specific needs.
+
+In a nutshell, you annotate your classes with `@Plugin` and their (`static`)
+creator methods with `@PluginFactory`. Last, you inform the Log4j plugin system
+to discover these custom classes. This can be done either using `packages`
+declared in your Log4j configuration or by various other ways described in
+link:plugins.html[plugin system documentation].
+
+[#extending-event-resolvers]
+=== Extending Event Resolvers
+
+All available xref:event-template-resolvers[event template resolvers] are simple
+plugins employed by `JsonTemplateLayout`. To add new ones, one just needs to
+create their own `EventResolver` and instruct its injection via a
+`@Plugin`-annotated `EventResolverFactory` class.
+
+For demonstration purposes, below we will create a `randomNumber` event resolver.
+Let's start with the actual resolver:
+
+[source,java]
+.Custom random number event resolver
+----
+package com.acme.logging.log4j.layout.template.json;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolver;
+import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+
+/**
+ * Resolves a random floating point number.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = ( [ range ] )
+ * range = number[]
+ * </pre>
+ *
+ * {@code range} is a number array with two elements, where the first number
+ * denotes the start (inclusive) and the second denotes the end (exclusive).
+ * {@code range} is optional and by default set to {@code [0, 1]}.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve a random number between 0 and 1:
+ *
+ * <pre>
+ * {
+ * "$resolver": "randomNumber"
+ * }
+ * </pre>
+ *
+ * Resolve a random number between -0.123 and 0.123:
+ *
+ * <pre>
+ * {
+ * "$resolver": "randomNumber",
+ * "range": [-0.123, 0.123]
+ * }
+ * </pre>
+ */
+public final class RandomNumberResolver implements EventResolver {
+
+ private final double loIncLimit;
+
+ private final double hiExcLimit;
+
+ RandomNumberResolver(final TemplateResolverConfig config) {
+ final List<Number> rangeArray = config.getList("range", Number.class);
+ if (rangeArray == null) {
+ this.loIncLimit = 0D;
+ this.hiExcLimit = 1D;
+ } else if (rangeArray.size() != 2) {
+ throw new IllegalArgumentException(
+ "range array must be of size two: " + config);
+ } else {
+ this.loIncLimit = rangeArray.get(0).doubleValue();
+ this.hiExcLimit = rangeArray.get(1).doubleValue();
+ if (loIncLimit > hiExcLimit) {
+ throw new IllegalArgumentException("invalid range: " + config);
+ }
+ }
+ }
+
+ static String getName() {
+ return "randomNumber";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent value,
+ final JsonWriter jsonWriter) {
+ final double randomNumber =
+ loIncLimit + (hiExcLimit - loIncLimit) * Math.random();
+ jsonWriter.writeNumber(randomNumber);
+ }
+
+}
+----
+
+Next create a `EventResolverFactory` class to register `RandomNumberResolver`
+into the Log4j plugin system.
+
+[source,java]
+.Resolver factory class to register `RandomNumberResolver` into the Log4j plugin system
+----
+package com.acme.logging.log4j.layout.template.json;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory;
+
+/**
+ * {@link RandomNumberResolver} factory.
+ */
+@Plugin(name = "RandomNumberResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class RandomNumberResolverFactory implements EventResolverFactory {
+
+ private static final RandomNumberResolverFactory INSTANCE =
+ new RandomNumberResolverFactory();
+
+ private RandomNumberResolverFactory() {}
+
+ @PluginFactory
+ public static RandomNumberResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return RandomNumberResolver.getName();
+ }
+
+ @Override
+ public RandomNumberResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new RandomNumberResolver(config);
+ }
+
+}
+----
+
+Almost complete. Last, we need to inform the Log4j plugin system to discover
+these custom classes:
+
+[source,xml]
+.Log4j configuration employing custom `randomNumber` resolver
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration packages="com.acme.logging.log4j.layout.template.json">
+ <!-- ... -->
+ <JsonTemplateLayout>
+ <EventTemplateAdditionalField
+ key="id"
+ format="JSON"
+ value='{"$resolver": "randomNumber", "range": [0, 1000000]}'/>
+ </JsonTemplateLayout>
+ <!-- ... -->
+</Configuration>
+----
+
+All available event template resolvers are located in
+`org.apache.logging.log4j.layout.template.json.resolver` package. It is a fairly
+rich resource for inspiration while implementing new resolvers.
+
+[#extending-template-resolver]
+=== Intercepting the Template Resolver Compiler
+
+`JsonTemplateLayout` allows interception of the template resolver compilation,
+which is the process converting a template into a Java function performing the
+JSON serialization. This interception mechanism is internally used to implement
+`eventTemplateRootObjectKey` and `eventTemplateAdditionalField` features. In a
+nutshell, one needs to create a `@Plugin`-annotated class extending from
+`EventResolverInterceptor` interface.
+
+To see the interception in action, check out the `EventRootObjectKeyInterceptor`
+class which is responsible for implementing the `eventTemplateRootObjectKey`
+feature:
+
+[source,java]
+.Event interceptor to add `eventTemplateRootObjectKey`, if present
+----
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
+import org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor;
+import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverInterceptor;
+
+/**
+ * Interceptor to add a root object key to the event template.
+ */
+@Plugin(name = "EventRootObjectKeyInterceptor", category = TemplateResolverInterceptor.CATEGORY)
+public class EventRootObjectKeyInterceptor implements EventResolverInterceptor {
+
+ private static final EventRootObjectKeyInterceptor INSTANCE =
+ new EventRootObjectKeyInterceptor();
+
+ private EventRootObjectKeyInterceptor() {}
+
+ @PluginFactory
+ public static EventRootObjectKeyInterceptor getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Object processTemplateBeforeResolverInjection(
+ final EventResolverContext context,
+ final Object node) {
+ String eventTemplateRootObjectKey = context.getEventTemplateRootObjectKey();
+ return eventTemplateRootObjectKey != null
+ ? Collections.singletonMap(eventTemplateRootObjectKey, node)
+ : node;
+ }
+
+}
+----
+
+Here, `processTemplateBeforeResolverInjection()` method checks if the user has
+provided an `eventTemplateRootObjectKey`. If so, it wraps the root `node` with a
+new object; otherwise, returns the `node` as is. Note that `node` refers to the
+root Java object of the event template read by `JsonReader`.
+
+[#extending-recycler]
+=== Extending Recycler Factories
+
+`recyclerFactory` input `String` read from the layout configuration is converted
+to a `RecyclerFactory` using the default `RecyclerFactoryConverter` extending
+from `TypeConverter<RecyclerFactory>`. If one wants to change this behavior,
+they simply need to add their own `TypeConverter<RecyclerFactory>` implementing
+`Comparable<TypeConverter<?>>` to prioritize their custom converter.
+
+[source,java]
+.Custom `TypeConverter` for `RecyclerFactory`
+----
+package com.acme.logging.log4j.layout.template.json;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
+
+@Plugin(name = "AcmeRecyclerFactoryConverter", category = TypeConverters.CATEGORY)
+public final class AcmeRecyclerFactoryConverter
+ implements TypeConverter<RecyclerFactory>, Comparable<TypeConverter<?>> {
+
+ @Override
+ public RecyclerFactory convert(final String recyclerFactorySpec) {
+ return AcmeRecyclerFactory.ofSpec(recyclerFactorySpec);
+ }
+
+ @Override
+ public int compareTo(final TypeConverter<?> ignored) {
+ return -1;
+ }
+
+}
+----
+
+Here note that `compareTo()` always returns -1 to rank it higher compared to
+other matching converters.
+
[#features]
== Features
@@ -1333,6 +1659,12 @@ alternatives.
| ✕
| ✕
| ✕
+
+| Custom resolvers?
+| ✓
+| ✕
+| ✕
+| ✕
|===
[#faq]
@@ -1356,7 +1688,7 @@ recursiveCollection[0] = recursiveCollection;
----
While the exact exception might vary, you will most like get a
-`StackOverflowError` while trying to render `recursiveCollection` into a
+`StackOverflowError` for trying to render `recursiveCollection` into a
`String`. Note that this is also the default behaviour for other Java standard
library methods, e.g., `Arrays.toString()`. Hence mind self references while
logging.
@@ -1376,9 +1708,9 @@ enabled. Take into account the following caveats:
* Serialization of ``MapMessage``s and ``ObjectMessage``s are mostly
garbage-free except for certain types (e.g., `BigDecimal`, `BigInteger`,
- ``Collection``s with the exception of `List`).
+ ``Collection``s, except `List`).
* link:lookups.html[Lookups] (that is, `${...}` variables) are not garbage-free.
-Don't forget to checkout link:#event-template-resolvers[the notes on garbage footprint of resolvers]
+Don't forget to check out xref:event-template-resolvers[the notes on garbage footprint of resolvers]
you employ in templates.
diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index 9e6916a..0490d73 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -628,13 +628,14 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
},
"exception_message": {
"${dollar}resolver": "exception",
- "field": "message",
- "stringified": true
+ "field": "message"
},
"stacktrace": {
"${dollar}resolver": "exception",
"field": "stackTrace",
- "stringified": true
+ "stackTrace": {
+ "stringified": true
+ }
}
},
"line_number": {
diff --git a/src/site/xdoc/manual/plugins.xml b/src/site/xdoc/manual/plugins.xml
index c6addc8..54de3d2 100644
--- a/src/site/xdoc/manual/plugins.xml
+++ b/src/site/xdoc/manual/plugins.xml
@@ -240,6 +240,9 @@
Unlike other plugins, the plugin name of a <code>TypeConverter</code> is purely cosmetic. Appropriate
type converters are looked up via the <code>Type</code> interface rather than via <code>Class<?></code>
objects only. Do note that <code>TypeConverter</code> plugins must have a default constructor.
+ When multiple converters match for a type, the first will be returned.
+ If any extends from <code>Comparable<TypeConverter<?>></code>,
+ it will be used for determining the order.
</p>
</subsection>
</section>