You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2019/06/11 02:50:17 UTC

[logging-log4j2] branch LOG4J2-2621 updated: LOG4J2-2621 - Provide compatibility for Log4j 2 2.x plugins

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch LOG4J2-2621
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/LOG4J2-2621 by this push:
     new 84fd87a  LOG4J2-2621 - Provide compatibility for Log4j 2 2.x plugins
84fd87a is described below

commit 84fd87acdb874711887178c7b000a7f835ede99a
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Mon Jun 10 19:50:06 2019 -0700

    LOG4J2-2621 - Provide compatibility for Log4j 2 2.x plugins
---
 log4j-core/pom.xml                                 |  13 ++
 .../log4j/core/config/plugins/PluginAliases.java   |  31 +++
 .../log4j/core/config/plugins/PluginAttribute.java | 101 ++++++++++
 .../config}/plugins/PluginBuilderAttribute.java    |  12 +-
 .../core/config/plugins/PluginBuilderFactory.java  |  31 +++
 .../log4j/core/config/plugins/PluginElement.java   |  38 ++++
 .../log4j/core/config/plugins/PluginFactory.java   |  34 ++++
 .../log4j/core/config/plugins/PluginNode.java      |  34 ++++
 .../log4j/core/config/plugins/PluginValue.java     |  39 ++++
 .../core/config/plugins/util/PluginBuilder.java    |  51 ++++-
 .../plugins/visitors/PluginAttributeVisitor.java   |  80 ++++++++
 .../visitors/PluginBuilderAttributeVisitor.java    |  51 +++++
 .../plugins/visitors/PluginElementVisitor.java     | 113 +++++++++++
 .../config/plugins/visitors/PluginNodeVisitor.java |  44 +++++
 .../plugins/visitors/PluginValueVisitor.java       |  57 ++++++
 .../core/time/internal/format/FastDateFormat.java  |  14 ++
 .../apache/logging/log4j/core/util/Builder.java    |  46 +++++
 .../log4j/core/util/datetime/FastDateFormat.java   | 213 +++++++++++++++++++++
 .../core/config/plugins/LegacyPluginTest.java      |  62 ++++++
 log4j-core/src/test/resources/legacy-plugins.xml   |  36 ++++
 .../log4j/plugins/PluginBuilderAttribute.java      |   2 +-
 .../plugins/visitors/AbstractPluginVisitor.java    |   1 -
 22 files changed, 1084 insertions(+), 19 deletions(-)

diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index 4ac43c1..d790b1d 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -31,6 +31,7 @@
     <log4jParentDir>${basedir}/..</log4jParentDir>
     <docLabel>Core Documentation</docLabel>
     <projectDir>/core</projectDir>
+    <log4j2-logstash-layout.version>0.18</log4j2-logstash-layout.version>
   </properties>
   <dependencies>
     <!-- Naturally, all implementations require the log4j-api JAR -->
@@ -311,6 +312,18 @@
       <artifactId>HdrHistogram</artifactId>
       <scope>test</scope>
     </dependency>
+    <!-- For testing log4j 2 2.x plugins -->
+    <dependency>
+      <groupId>com.vlkan.log4j2</groupId>
+      <artifactId>log4j2-logstash-layout</artifactId>
+      <version>${log4j2-logstash-layout.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.github.ivandzf</groupId>
+      <artifactId>log4j2-custom-layout</artifactId>
+      <version>1.1.0</version>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java
new file mode 100644
index 0000000..2b06df6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a list of aliases for a Plugin, PluginAttribute, or PluginBuilderAttribute.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
+public @interface PluginAliases {
+
+    String[] value();
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
new file mode 100644
index 0000000..03e78d8
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.plugins.PluginVisitorStrategy;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
+ * used based on the type this annotation is attached to. Thus, for primitive types, the default<i>Type</i>
+ * attribute will be used for some <i>Type</i>. However, for more complex types (including enums), the default
+ * string value is used instead and should correspond to the string that would correctly convert to the appropriate
+ * enum value using {@link Enum#valueOf(Class, String) Enum.valueOf}.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@PluginVisitorStrategy(PluginAttributeVisitor.class)
+public @interface PluginAttribute {
+
+    /**
+     * Specifies the default boolean value to use.
+     */
+    boolean defaultBoolean() default false;
+
+    /**
+     * Specifies the default byte value to use.
+     */
+    byte defaultByte() default 0;
+
+    /**
+     * Specifies the default byte value to use.
+     */
+    char defaultChar() default 0;
+
+    /**
+     * Specifies the default {@link Class} value to use.
+     */
+    Class<?> defaultClass() default Object.class;
+
+    /**
+     * Specifies the default double floating point value to use.
+     */
+    double defaultDouble() default 0.0d;
+
+    /**
+     * Specifies the default floating point value to use.
+     */
+    float defaultFloat() default 0.0f;
+
+    /**
+     * Specifies the default integer value to use.
+     */
+    int defaultInt() default 0;
+
+    /**
+     * Specifies the default long value to use.
+     */
+    long defaultLong() default 0L;
+
+    /**
+     * Specifies the default long value to use.
+     */
+    short defaultShort() default 0;
+
+    /**
+     * Specifies the default value this attribute should use if none is provided or if the provided value is invalid.
+     */
+    String defaultString() default Strings.EMPTY;
+
+    // TODO: could we allow a blank value and infer the attribute name through reflection?
+    /**
+     * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
+     */
+    String value();
+
+    /**
+     * Indicates that this attribute is a sensitive one that shouldn't be logged directly. Such attributes will instead
+     * be output as a hashed value.
+     */
+    boolean sensitive() default false;
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
similarity index 82%
copy from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
copy to log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
index c7fce2f..e34369f 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
@@ -15,19 +15,17 @@
  * limitations under the license.
  */
 
-package org.apache.logging.log4j.plugins;
+package org.apache.logging.log4j.core.config.plugins;
 
-import org.apache.logging.log4j.plugins.visitors.PluginBuilderAttributeVisitor;
+import org.apache.logging.log4j.plugins.PluginVisitorStrategy;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
 import org.apache.logging.log4j.util.Strings;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * Marks a field as a Plugin Attribute.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
new file mode 100644
index 0000000..0ea7221
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks a method as a factory for custom Plugin builders.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PluginBuilderFactory {
+    // empty
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
new file mode 100644
index 0000000..9c1156b
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
+import org.apache.logging.log4j.plugins.PluginVisitorStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@PluginVisitorStrategy(PluginElementVisitor.class)
+public @interface PluginElement {
+
+    /**
+     * Identifies the case-insensitive element name (or attribute name) this corresponds with in a configuration file.
+     */
+    String value();
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
new file mode 100644
index 0000000..2e25631
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a Method as the factory to create the plugin. This annotation should only be used on a {@code static}
+ * method, and its parameters should be annotated with the appropriate Plugin annotations.
+ * <p>
+ * There can only be one factory method per class.
+ * </p>
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PluginFactory {
+    // empty
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
new file mode 100644
index 0000000..b359253
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
+import org.apache.logging.log4j.plugins.PluginVisitorStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a Plugin configuration Node.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@PluginVisitorStrategy(PluginNodeVisitor.class)
+public @interface PluginNode {
+    // empty
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
new file mode 100644
index 0000000..4b12c77
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.config.plugins;
+
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
+import org.apache.logging.log4j.plugins.PluginVisitorStrategy;
+
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
+ * used as a placeholder value somewhere.
+ *
+ * @see org.apache.logging.log4j.core.config.PropertiesPlugin
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@PluginVisitorStrategy(PluginValueVisitor.class)
+public @interface PluginValue {
+
+    String value();
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
index 5982af4..e05523a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
@@ -27,6 +27,8 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
 
 import org.apache.logging.log4j.Logger;
@@ -34,12 +36,11 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationException;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.plugins.util.Builder;
-import org.apache.logging.log4j.util.ReflectionUtil;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.PluginAliases;
 import org.apache.logging.log4j.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.util.Builder;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
@@ -47,6 +48,7 @@ import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
 import org.apache.logging.log4j.plugins.visitors.PluginVisitor;
 import org.apache.logging.log4j.plugins.visitors.PluginVisitors;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ReflectionUtil;
 import org.apache.logging.log4j.util.StringBuilders;
 
 /**
@@ -64,6 +66,7 @@ public class PluginBuilder implements Builder<Object> {
     private Node node;
     private LogEvent event;
     private Substitutor substitutor;
+    private ConcurrentMap<String, Boolean> aliases = new ConcurrentHashMap<>();
 
     /**
      * Constructs a PluginBuilder for a given PluginType.
@@ -160,23 +163,30 @@ public class PluginBuilder implements Builder<Object> {
                 TypeUtil.isAssignable(Builder.class, method.getReturnType())) {
                 ReflectionUtil.makeAccessible(method);
                 return (Builder<?>) method.invoke(null);
+            } else if (method.isAnnotationPresent(org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory.class) &&
+                    Modifier.isStatic(method.getModifiers()) &&
+                    TypeUtil.isAssignable(org.apache.logging.log4j.core.util.Builder.class, method.getReturnType())) {
+                ReflectionUtil.makeAccessible(method);
+                return new BuilderWrapper((org.apache.logging.log4j.core.util.Builder<?>) method.invoke(null));
             }
         }
         return null;
     }
 
     private void injectFields(final Builder<?> builder) throws IllegalAccessException {
-        final List<Field> fields = TypeUtil.getAllDeclaredFields(builder.getClass());
+        final Object target = builder instanceof BuilderWrapper ? ((BuilderWrapper) builder).getBuilder() : builder;
+        final List<Field> fields = TypeUtil.getAllDeclaredFields(target.getClass());
         AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true);
         final StringBuilder log = new StringBuilder();
         boolean invalid = false;
         StringBuilder reason = new StringBuilder();
         for (final Field field : fields) {
-            log.append(log.length() == 0 ? simpleName(builder) + "(" : ", ");
+            log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
             final Annotation[] annotations = field.getDeclaredAnnotations();
             final String[] aliases = extractPluginAliases(annotations);
-            for (final Annotation a : annotations) {
-                if (a instanceof PluginAliases) {
+            for (Annotation a : annotations) {
+                if (a instanceof PluginAliases ||
+                        a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
                     continue; // already processed
                 }
                 final PluginVisitor<? extends Annotation, Configuration> visitor =
@@ -189,13 +199,13 @@ public class PluginBuilder implements Builder<Object> {
                         .visit(configuration, node, substitutor, log);
                     // don't overwrite default values if the visitor gives us no value to inject
                     if (value != null) {
-                        field.set(builder, value);
+                        field.set(target, value);
                     }
                 }
             }
             final Collection<ConstraintValidator<?>> validators =
                 ConstraintValidators.findValidators(annotations);
-            final Object value = field.get(builder);
+            final Object value = field.get(target);
             for (final ConstraintValidator<?> validator : validators) {
                 if (!validator.isValid(field.getName(), value)) {
                     invalid = true;
@@ -233,7 +243,8 @@ public class PluginBuilder implements Builder<Object> {
 
     private static Method findFactoryMethod(final Class<?> clazz) {
         for (final Method method : clazz.getDeclaredMethods()) {
-            if (method.isAnnotationPresent(PluginFactory.class) &&
+            if ((method.isAnnotationPresent(PluginFactory.class) ||
+                 method.isAnnotationPresent(org.apache.logging.log4j.core.config.plugins.PluginFactory.class)) &&
                 Modifier.isStatic(method.getModifiers())) {
                 ReflectionUtil.makeAccessible(method);
                 return method;
@@ -252,11 +263,13 @@ public class PluginBuilder implements Builder<Object> {
             log.append(log.length() == 0 ? factory.getName() + "(" : ", ");
             final String[] aliases = extractPluginAliases(annotations[i]);
             for (final Annotation a : annotations[i]) {
-                if (a instanceof PluginAliases) {
+                if (a instanceof PluginAliases ||
+                        a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
                     continue; // already processed
                 }
                 final PluginVisitor<? extends Annotation, Configuration> visitor = PluginVisitors.findVisitor(
                     a.annotationType());
+
                 if (visitor != null) {
                     final Object value = visitor.setAliases(aliases)
                         .setAnnotation(a)
@@ -294,6 +307,8 @@ public class PluginBuilder implements Builder<Object> {
         for (final Annotation a : parmTypes) {
             if (a instanceof PluginAliases) {
                 aliases = ((PluginAliases) a).value();
+            } else if (a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
+                aliases = ((org.apache.logging.log4j.core.config.plugins.PluginAliases) a).value();
             }
         }
         return aliases;
@@ -346,4 +361,20 @@ public class PluginBuilder implements Builder<Object> {
             return strSubstitutor.replace(event, str);
         }
     }
+
+    public static class BuilderWrapper<T> implements Builder<T> {
+        private final org.apache.logging.log4j.core.util.Builder<T> builder;
+
+        BuilderWrapper(org.apache.logging.log4j.core.util.Builder<T> builder) {
+            this.builder = builder;
+        }
+
+        public T build() {
+            return builder.build();
+        }
+
+        org.apache.logging.log4j.core.util.Builder<T> getBuilder() {
+            return builder;
+        }
+    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
new file mode 100644
index 0000000..bf3da4f
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.visitors;
+
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.visitors.AbstractPluginVisitor;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.util.StringBuilders;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * @deprecated. Provided to support legacy plugins.
+ */
+public class PluginAttributeVisitor extends AbstractPluginVisitor<PluginAttribute, Object> {
+    public PluginAttributeVisitor() {
+        super(PluginAttribute.class);
+    }
+
+    @Override
+    public Object visit(final Object unused, final Node node, final Function<String, String> substitutor,
+                        final StringBuilder log) {
+        final String name = this.annotation.value();
+        final Map<String, String> attributes = node.getAttributes();
+        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
+        final String replacedValue = substitutor.apply(rawValue);
+        final Object defaultValue = findDefaultValue(substitutor);
+        final Object value = convert(replacedValue, defaultValue);
+        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
+        StringBuilders.appendKeyDqValue(log, name, debugValue);
+        return value;
+    }
+
+    private Object findDefaultValue(Function<String, String> substitutor) {
+        if (this.conversionType == int.class || this.conversionType == Integer.class) {
+            return this.annotation.defaultInt();
+        }
+        if (this.conversionType == long.class || this.conversionType == Long.class) {
+            return this.annotation.defaultLong();
+        }
+        if (this.conversionType == boolean.class || this.conversionType == Boolean.class) {
+            return this.annotation.defaultBoolean();
+        }
+        if (this.conversionType == float.class || this.conversionType == Float.class) {
+            return this.annotation.defaultFloat();
+        }
+        if (this.conversionType == double.class || this.conversionType == Double.class) {
+            return this.annotation.defaultDouble();
+        }
+        if (this.conversionType == byte.class || this.conversionType == Byte.class) {
+            return this.annotation.defaultByte();
+        }
+        if (this.conversionType == char.class || this.conversionType == Character.class) {
+            return this.annotation.defaultChar();
+        }
+        if (this.conversionType == short.class || this.conversionType == Short.class) {
+            return this.annotation.defaultShort();
+        }
+        if (this.conversionType == Class.class) {
+            return this.annotation.defaultClass();
+        }
+        return substitutor.apply(this.annotation.defaultString());
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
new file mode 100644
index 0000000..7c5ed21
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.config.plugins.visitors;
+
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.visitors.AbstractPluginVisitor;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.util.StringBuilders;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * @deprecated Provided for support for PluginBuilderAttribute.
+ */
+public class PluginBuilderAttributeVisitor extends AbstractPluginVisitor<PluginBuilderAttribute, Object> {
+
+    public PluginBuilderAttributeVisitor() {
+        super(PluginBuilderAttribute.class);
+    }
+
+    @Override
+    public Object visit(final Object unused, final Node node, final Function<String, String> substitutor,
+                        final StringBuilder log) {
+        final String overridden = this.annotation.value();
+        final String name = overridden.isEmpty() ? this.member.getName() : overridden;
+        final Map<String, String> attributes = node.getAttributes();
+        final String rawValue = removeAttributeValue(attributes, name, this.aliases);
+        final String replacedValue = substitutor.apply(rawValue);
+        final Object value = convert(replacedValue, null);
+        final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
+        StringBuilders.appendKeyDqValue(log, name, debugValue);
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
new file mode 100644
index 0000000..a5380b8
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
@@ -0,0 +1,113 @@
+/*
+ * 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.visitors;
+
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.visitors.AbstractPluginVisitor;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ *  @deprecated. Provided to support legacy plugins.
+ */
+public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement, Object> {
+    public PluginElementVisitor() {
+        super(PluginElement.class);
+    }
+
+    @Override
+    public Object visit(final Object unused, final Node node, final Function<String, String> substitutor,
+                        final StringBuilder log) {
+        final String name = this.annotation.value();
+        if (this.conversionType.isArray()) {
+            setConversionType(this.conversionType.getComponentType());
+            final List<Object> values = new ArrayList<>();
+            final Collection<Node> used = new ArrayList<>();
+            log.append("={");
+            boolean first = true;
+            for (final Node child : node.getChildren()) {
+                final PluginType<?> childType = child.getType();
+                if (name.equalsIgnoreCase(childType.getElementName()) ||
+                    this.conversionType.isAssignableFrom(childType.getPluginClass())) {
+                    if (!first) {
+                        log.append(", ");
+                    }
+                    first = false;
+                    used.add(child);
+                    final Object childObject = child.getObject();
+                    if (childObject == null) {
+                        LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
+                        continue;
+                    }
+                    if (childObject.getClass().isArray()) {
+                        log.append(Arrays.toString((Object[]) childObject)).append('}');
+                        node.getChildren().removeAll(used);
+                        return childObject;
+                    }
+                    log.append(child.toString());
+                    values.add(childObject);
+                }
+            }
+            log.append('}');
+            // note that we need to return an empty array instead of null if the types are correct
+            if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
+                LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
+                    name, values.get(0).getClass(), this.conversionType);
+                return null;
+            }
+            node.getChildren().removeAll(used);
+            // we need to use reflection here because values.toArray() will cause type errors at runtime
+            final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
+            for (int i = 0; i < array.length; i++) {
+                array[i] = values.get(i);
+            }
+            return array;
+        }
+        final Node namedNode = findNamedNode(name, node.getChildren());
+        if (namedNode == null) {
+            log.append(name).append("=null");
+            return null;
+        }
+        log.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
+        node.getChildren().remove(namedNode);
+        return namedNode.getObject();
+    }
+
+    private Node findNamedNode(final String name, final Iterable<Node> children) {
+        for (final Node child : children) {
+            final PluginType<?> childType = child.getType();
+            if (childType == null) {
+                //System.out.println();
+            }
+            if (name.equalsIgnoreCase(childType.getElementName()) ||
+                this.conversionType.isAssignableFrom(childType.getPluginClass())) {
+                // FIXME: check child.getObject() for null?
+                // doing so would be more consistent with the array version
+                return child;
+            }
+        }
+        return null;
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
new file mode 100644
index 0000000..5e651b1
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.config.plugins.visitors;
+
+import org.apache.logging.log4j.core.config.plugins.PluginNode;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.visitors.AbstractPluginVisitor;
+
+import java.util.function.Function;
+
+/**
+ *  @deprecated. Provided to support legacy plugins.
+ */
+public class PluginNodeVisitor extends AbstractPluginVisitor<PluginNode, Object> {
+    public PluginNodeVisitor() {
+        super(PluginNode.class);
+    }
+
+    @Override
+    public Object visit(final Object unused, final Node node, final Function<String, String> substitutor,
+                        final StringBuilder log) {
+        if (this.conversionType.isInstance(node)) {
+            log.append("Node=").append(node.getName());
+            return node;
+        }
+        LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
+        return null;
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
new file mode 100644
index 0000000..deee939
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
@@ -0,0 +1,57 @@
+/*
+ * 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.visitors;
+
+import org.apache.logging.log4j.core.config.plugins.PluginValue;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.visitors.AbstractPluginVisitor;
+import org.apache.logging.log4j.util.StringBuilders;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.function.Function;
+
+/**
+ *  @deprecated. Provided to support legacy plugins.
+ */
+public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue, Object> {
+    public PluginValueVisitor() {
+        super(PluginValue.class);
+    }
+
+    @Override
+    public Object visit(final Object unused, final Node node, final Function<String, String> substitutor,
+                        final StringBuilder log) {
+        final String name = this.annotation.value();
+        final String elementValue = node.getValue();
+        final String attributeValue = node.getAttributes().get("value");
+        String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
+        if (Strings.isNotEmpty(elementValue)) {
+            if (Strings.isNotEmpty(attributeValue)) {
+                LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
+                                " value ({}). Please specify only one value. Using the element value.",
+                        node.getName(), attributeValue, elementValue);
+            }
+            rawValue = elementValue;
+        } else {
+            rawValue = removeAttributeValue(node.getAttributes(), "value");
+        }
+        final String value = substitutor.apply(rawValue);
+        StringBuilders.appendKeyDqValue(log, name, value);
+        return value;
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
index c452a86..5685884 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
@@ -372,6 +372,20 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
         return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
     }
 
+    /**
+     * <p>Constructs a new FastDateFormat.</p>
+     *
+     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
+     * @param timeZone  non-null time zone to use
+     * @param locale  non-null locale to use
+     * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing.  If centuryStart is null, defaults to now - 80 years
+     * @throws NullPointerException if pattern, timeZone, or locale is null.
+     * @since 3.0
+     */
+    public static FastDateFormat getDateTimeInstance(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+        return new FastDateFormat(pattern, timeZone, locale, centuryStart);
+    }
+
     // Constructor
     //-----------------------------------------------------------------------
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
new file mode 100644
index 0000000..9acc9d5
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.util;
+
+/**
+ * A type of builder that can be used to configure and create a instances using a Java DSL instead of
+ * through a configuration file. These builders are primarily useful for internal code and unit tests, but they can
+ * technically be used as a verbose alternative to configuration files.
+ *
+ * <p>
+ *     When creating <em>plugin</em> builders, it is customary to create the builder class as a public static inner class
+ *     called {@code Builder}. For instance, the builder class for
+ *     {@link org.apache.logging.log4j.core.layout.PatternLayout PatternLayout} would be
+ *     {@code PatternLayout.Builder}.
+ * </p>
+ *
+ * @param <T> This builder creates instances of this class.
+ * @deprecated Present only for compatibility with Log4j 2 2.x plugins. Use Builder from log4j-plugins.
+ */
+public interface Builder<T> {
+
+    /**
+     * Builds the object after all configuration has been set. This will use default values for any
+     * unspecified attributes for the object.
+     *
+     * @return the configured instance.
+     * @throws org.apache.logging.log4j.core.config.ConfigurationException if there was an error building the
+     * object.
+     */
+    T build();
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
new file mode 100644
index 0000000..691b268
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
@@ -0,0 +1,213 @@
+/*
+ * 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.util.datetime;
+
+import java.text.FieldPosition;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.logging.log4j.core.time.internal.format.*;
+
+/**
+ *
+ * @deprecated Use {@link org.apache.logging.log4j.core.time.internal.format.FastDateFormat}
+ */
+public class FastDateFormat extends Format implements DateParser, DatePrinter {
+
+    private org.apache.logging.log4j.core.time.internal.format.FastDateFormat formatter = null;
+
+    public static FastDateFormat getInstance() {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance());
+    }
+
+    public static FastDateFormat getInstance(final String pattern) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern));
+    }
+
+    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, timeZone));
+    }
+
+    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, locale));
+    }
+
+    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, timeZone, locale));
+    }
+
+    public static FastDateFormat getDateInstance(final int style) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style));
+    }
+
+    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, locale));
+    }
+
+    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, timeZone));
+    }
+
+    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, timeZone, locale));
+    }
+
+    public static FastDateFormat getTimeInstance(final int style) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style));
+    }
+
+    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, locale));
+    }
+
+    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, timeZone));
+    }
+
+    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, timeZone, locale));
+    }
+
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle));
+    }
+
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, locale));
+    }
+
+    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, timeZone));
+    }
+
+    public static FastDateFormat getDateTimeInstance(
+            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
+        return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale));
+    }
+
+    private FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat formatter) {
+        this.formatter = formatter;
+    }
+
+    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
+        formatter = org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern,
+                timeZone, locale);
+    }
+
+    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+        formatter = org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(pattern,
+                timeZone, locale, centuryStart);
+    }
+
+    @Override
+    public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) {
+        return formatter.format(obj, toAppendTo, pos);
+    }
+
+    @Override
+    public String format(final long millis) {
+        return formatter.format(millis);
+    }
+
+    @Override
+    public String format(final Date date) {
+        return formatter.format(date);
+    }
+
+    @Override
+    public String format(final Calendar calendar) {
+        return formatter.format(calendar);
+    }
+
+    @Override
+    public <B extends Appendable> B format(final long millis, final B buf) {
+        return formatter.format(millis, buf);
+    }
+
+    @Override
+    public <B extends Appendable> B format(final Date date, final B buf) {
+        return formatter.format(date, buf);
+    }
+
+    @Override
+    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
+        return formatter.format(calendar, buf);
+    }
+
+    @Override
+    public Date parse(final String source) throws ParseException {
+        return formatter.parse(source);
+    }
+
+    @Override
+    public Date parse(final String source, final ParsePosition pos) {
+        return formatter.parse(source, pos);
+    }
+
+    @Override
+    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
+        return formatter.parse(source, pos, calendar);
+    }
+
+    @Override
+    public Object parseObject(final String source, final ParsePosition pos) {
+        return formatter.parseObject(source, pos);
+    }
+
+    @Override
+    public String getPattern() {
+        return formatter.getPattern();
+    }
+
+    @Override
+    public TimeZone getTimeZone() {
+        return formatter.getTimeZone();
+    }
+
+    @Override
+    public Locale getLocale() {
+        return formatter.getLocale();
+    }
+
+    public int getMaxLengthEstimate() {
+        return formatter.getMaxLengthEstimate();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof FastDateFormat == false) {
+            return false;
+        }
+        final FastDateFormat other = (FastDateFormat) obj;
+        // no need to check parser, as it has same invariants as printer
+        return formatter.equals(other.formatter);
+    }
+
+    @Override
+    public int hashCode() {
+        return formatter.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return formatter.toString();
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java
new file mode 100644
index 0000000..3aad3c6
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.config.plugins;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * Class Description goes here.
+ */
+public class LegacyPluginTest {
+
+    private static final String CONFIG_FILE = "legacy-plugins.xml";
+
+    @Test
+    public void testLegacy() throws Exception {
+        LoggerContext context = Configurator.initialize("LegacyTest", null, CONFIG_FILE);
+        assertNotNull("No Logger Context", context);
+        Configuration configuration = ((org.apache.logging.log4j.core.LoggerContext) context).getConfiguration();
+        assertNotNull("No Configuration", configuration);
+        assertTrue("Incorrect Configuration class " + configuration.getClass().getName(),
+                configuration instanceof XmlConfiguration);
+        for (Map.Entry<String, Appender> entry : configuration.getAppenders().entrySet()) {
+            if (entry.getKey().equalsIgnoreCase("console")) {
+                Layout layout = entry.getValue().getLayout();
+                assertNotNull("No layout for Console Appender");
+                String name = layout.getClass().getSimpleName();
+                assertTrue("Incorrect Layout class. Expected LogstashLayout, Actual " + name,
+                        name.equals("LogstashLayout"));
+            } else if (entry.getKey().equalsIgnoreCase("customConsole")) {
+                Layout layout = entry.getValue().getLayout();
+                assertNotNull("No layout for CustomConsole Appender");
+                String name = layout.getClass().getSimpleName();
+                assertTrue("Incorrect Layout class. Expected CustomConsoleLayout, Actual " + name,
+                        name.equals("CustomConsoleLayout"));
+            }
+        }
+    }
+}
diff --git a/log4j-core/src/test/resources/legacy-plugins.xml b/log4j-core/src/test/resources/legacy-plugins.xml
new file mode 100644
index 0000000..725be17
--- /dev/null
+++ b/log4j-core/src/test/resources/legacy-plugins.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<Configuration status="ERROR" name="XMLConfigTest">
+
+  <Appenders>
+    <Console name="Console" target="SYSTEM_OUT">
+      <LogstashLayout dateTimeFormatPattern="yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" prettyPrintEnabled="true"
+        stackTraceEnabled="true"/>
+    </Console>
+    <Console name="customConsole">
+      <CustomConsoleLayout/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Root level="trace">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
index c7fce2f..ed22bb9 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
@@ -31,7 +31,7 @@ import java.lang.annotation.Target;
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
 @PluginVisitorStrategy(PluginBuilderAttributeVisitor.class)
 public @interface PluginBuilderAttribute {
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visitors/AbstractPluginVisitor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visitors/AbstractPluginVisitor.java
index b98dc09..01e1e66 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visitors/AbstractPluginVisitor.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visitors/AbstractPluginVisitor.java
@@ -26,7 +26,6 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Member;
 import java.util.Map;
 import java.util.Objects;
-import java.util.function.Function;
 
 /**
  * Base class for PluginVisitor implementations. Provides convenience methods as well as all method implementations