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 2020/11/06 12:44:48 UTC

[logging-log4j2] branch master updated (a889a95 -> 5127846)

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

vy pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git.


    from a889a95  Only generate doap file in parent project. Revert Checkstyle plugin version change so site will build
     new a8d6e69  Try improving RollingDirectTimeNewDirectoryTest to alleviate test report failures.
     new f5d8780  Upload test reports in GitHub Actions.
     new 8682e35  Workarounds for Windows-specific "file being used by another process" failures.
     new 80f62c4  LOG4J2-620 ReconfigurationDeadlockTest rewrite to prevent spurious failures.
     new 5c938e1  Delete unused BasicLayout.
     new 17e074c  Catch all type of exceptions in AppenderControl.
     new 5127846  LOG4J2-2936 Add message parameter resolver to JSON template layout.

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/maven.yml                        |  29 +--
 .../logging/log4j/junit/AbstractFileCleaner.java   |   5 +-
 .../logging/log4j/junit/CleanUpDirectories.java    |   6 +-
 .../apache/logging/log4j/junit/CleanUpFiles.java   |   6 +-
 .../org/apache/logging/log4j/util/SneakyThrow.java |  13 ++
 .../logging/log4j/core/config/AppenderControl.java |   8 +-
 .../log4j/core/appender/FailoverAppenderTest.java  |   2 +-
 .../core/appender/RandomAccessFileManagerTest.java |  13 +-
 .../rolling/RollingDirectTimeNewDirectoryTest.java |  71 ++++---
 .../async/AsyncAppenderExceptionHandlingTest.java  |  97 ++++++++++
 .../core/config/AppenderControlArraySetTest.java   |   2 +-
 .../core/config/ReconfigurationDeadlockTest.java   | 209 +++++++++++++--------
 .../log4j/core/config/xml/XmlSchemaTest.java       |   2 +-
 .../log4j/test/SomethingThatUsesLogging.java       |  36 ----
 .../log4j/test/appender/DeadlockAppender.java      |  84 ---------
 .../log4j/test/appender/FailOnceAppender.java      |  91 +++++++--
 .../log4j/test/appender/UsesLoggingAppender.java   |  67 -------
 .../logging/log4j/test/layout/BasicLayout.java     |  57 ------
 ....xml => AsyncAppenderExceptionHandlingTest.xml} |  18 +-
 .../test/resources/reconfiguration-deadlock.xml    |  25 ++-
 .../json/resolver/EventResolverFactories.java      |   1 +
 .../template/json/resolver/LevelResolver.java      |   2 +-
 .../json/resolver/MessageParameterResolver.java    | 131 +++++++++++++
 ...y.java => MessageParameterResolverFactory.java} |  14 +-
 .../template/json/JsonTemplateLayoutTest.java      |  51 +++++
 .../asciidoc/manual/json-template-layout.adoc.vm   |  52 +++++
 26 files changed, 658 insertions(+), 434 deletions(-)
 create mode 100644 log4j-api/src/test/java/org/apache/logging/log4j/util/SneakyThrow.java
 create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java
 delete mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/test/SomethingThatUsesLogging.java
 delete mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
 delete mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
 delete mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
 copy log4j-core/src/test/resources/{XmlConfigurationSecurity.xml => AsyncAppenderExceptionHandlingTest.xml} (76%)
 create mode 100644 log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
 copy log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/{LevelResolverFactory.java => MessageParameterResolverFactory.java} (69%)


[logging-log4j2] 03/07: Workarounds for Windows-specific "file being used by another process" failures.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8682e3537fd2d6e89b16c2ccc84c705a8f818e06
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Tue Sep 22 21:49:52 2020 +0200

    Workarounds for Windows-specific "file being used by another process" failures.
---
 .github/workflows/maven.yml                                 |  2 +-
 .../org/apache/logging/log4j/junit/AbstractFileCleaner.java |  5 ++++-
 .../org/apache/logging/log4j/junit/CleanUpDirectories.java  |  6 ++++--
 .../java/org/apache/logging/log4j/junit/CleanUpFiles.java   |  6 ++++--
 .../log4j/core/appender/RandomAccessFileManagerTest.java    | 13 ++++++-------
 5 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 5bdb2f7..87fea70 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -52,7 +52,7 @@ jobs:
 
       - name: Build with Maven (Windows)
         if: runner.os == 'Windows'
-        run: ./mvnw -V -B -e -DtrimStackTrace=false "-Dmaven.test.failure.ignore=true" "-Dsurefire.rerunFailingTestsCount=1" --global-toolchains ".github\workflows\maven-toolchains.xml" verify
+        run: ./mvnw -V -B -e "-DtrimStackTrace=false" "-Dmaven.test.failure.ignore=true" "-Dsurefire.rerunFailingTestsCount=1" "-Dlog4j2.junit.fileCleanerSleepPeriodMillis=1000" --global-toolchains ".github\workflows\maven-toolchains.xml" verify
 
       - name: Inspect environment (MacOS)
         if: runner.os == 'macOS'
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractFileCleaner.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractFileCleaner.java
index c5d2bc4..ce57b80 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractFileCleaner.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractFileCleaner.java
@@ -34,8 +34,11 @@ import java.util.stream.Collectors;
 import static org.junit.jupiter.api.Assertions.fail;
 
 abstract class AbstractFileCleaner implements BeforeEachCallback, AfterEachCallback {
+
     private static final int MAX_TRIES = Integer.getInteger("log4j2.junit.fileCleanerMaxTries", 10);
 
+    private static final int SLEEP_PERIOD_MILLIS = Integer.getInteger("log4j2.junit.fileCleanerSleepPeriodMillis", 200);
+
     @Override
     public void beforeEach(final ExtensionContext context) throws Exception {
         clean(context);
@@ -64,7 +67,7 @@ abstract class AbstractFileCleaner implements BeforeEachCallback, AfterEachCallb
                         failures.put(path, e);
                     }
                     try {
-                        TimeUnit.MILLISECONDS.sleep(200);
+                        TimeUnit.MILLISECONDS.sleep(SLEEP_PERIOD_MILLIS);
                     } catch (final InterruptedException ignored) {
                         failures.put(path, new InterruptedIOException());
                         Thread.currentThread().interrupt();
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpDirectories.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpDirectories.java
index 6c398e6..151bb85 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpDirectories.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpDirectories.java
@@ -28,9 +28,11 @@ import java.lang.annotation.Target;
 
 /**
  * JUnit extension to automatically clean up a list of directories and their contents before and after test execution.
- * This will automatically retry deletion up to 10 times per file while pausing a short duration each time.
- * This can be overridden with the system property {@code log4j2.junit.fileCleanerMaxTries}.
+ * This will automatically retry deletion up to 10 times per file while pausing for 200ms each time.
+ * These can be overridden with system properties {@code log4j2.junit.fileCleanerMaxTries} and
+ * {@code log4j2.junit.fileCleanerSleepPeriodMillis}.
  *
+ * @see DirectoryCleaner
  * @see CleanUpFiles
  * @since 2.14.0
  */
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpFiles.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpFiles.java
index a3b9ca6..e00ca42 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpFiles.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanUpFiles.java
@@ -28,9 +28,11 @@ import java.lang.annotation.Target;
 
 /**
  * JUnit extension to automatically clean up a list of files before and after test execution.
- * This will automatically retry deletion up to 10 times per file while pausing a short duration each time.
- * This can be overridden with the system property {@code log4j2.junit.fileCleanerMaxTries}.
+ * This will automatically retry deletion up to 10 times per file while pausing for 200ms each time.
+ * These can be overridden with system properties {@code log4j2.junit.fileCleanerMaxTries} and
+ * {@code log4j2.junit.fileCleanerSleepPeriodMillis}.
  *
+ * @see FileCleaner
  * @see CleanUpDirectories
  * @since 2.14.0
  */
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java
index 62b2bec..6227f37 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java
@@ -38,12 +38,11 @@ public class RandomAccessFileManagerTest {
 
     /**
      * Test method for
-     * {@link org.apache.logging.log4j.core.appender.RandomAccessFileManager#writeBytes(byte[], int, int)}
-     * .
+     * {@link org.apache.logging.log4j.core.appender.RandomAccessFileManager#writeBytes(byte[], int, int)}.
      */
     @Test
     public void testWrite_multiplesOfBufferSize() throws IOException {
-        final File file = new File(tempDir, "random-access-file.bin");
+        final File file = new File(tempDir, "testWrite_multiplesOfBufferSize.bin");
         try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             final OutputStream os = NullOutputStream.getInstance();
             final RandomAccessFileManager manager = new RandomAccessFileManager(null, raf, file.getName(),
@@ -65,7 +64,7 @@ public class RandomAccessFileManagerTest {
      */
     @Test
     public void testWrite_dataExceedingBufferSize() throws IOException {
-        final File file = new File(tempDir, "random-access-file.bin");
+        final File file = new File(tempDir, "testWrite_dataExceedingBufferSize.bin");
         try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             final OutputStream os = NullOutputStream.getInstance();
             final RandomAccessFileManager manager = new RandomAccessFileManager(null, raf, file.getName(),
@@ -84,7 +83,7 @@ public class RandomAccessFileManagerTest {
 
     @Test
     public void testConfigurableBufferSize() throws IOException {
-        final File file = new File(tempDir, "random-access-file.bin");
+        final File file = new File(tempDir, "testConfigurableBufferSize.bin");
         try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             final OutputStream os = NullOutputStream.getInstance();
             final int bufferSize = 4 * 1024;
@@ -100,7 +99,7 @@ public class RandomAccessFileManagerTest {
 
     @Test
     public void testWrite_dataExceedingMinBufferSize() throws IOException {
-        final File file = new File(tempDir, "random-access-file.bin");
+        final File file = new File(tempDir, "testWrite_dataExceedingMinBufferSize.bin");
         try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             final OutputStream os = NullOutputStream.getInstance();
             final int bufferSize = 1;
@@ -121,7 +120,7 @@ public class RandomAccessFileManagerTest {
     @Test
     public void testAppendDoesNotOverwriteExistingFile() throws IOException {
         final boolean isAppend = true;
-        final File file = new File(tempDir, "random-access-file.bin");
+        final File file = new File(tempDir, "testAppendDoesNotOverwriteExistingFile.bin");
         assertEquals(0, file.length());
 
         final byte[] bytes = new byte[4 * 1024];


[logging-log4j2] 06/07: Catch all type of exceptions in AppenderControl.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 17e074c69689c407b27f7418817dd6ca609dfa0b
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Thu Nov 5 15:51:16 2020 +0100

    Catch all type of exceptions in AppenderControl.
---
 .../org/apache/logging/log4j/util/SneakyThrow.java | 13 +++
 .../logging/log4j/core/config/AppenderControl.java |  8 +-
 .../log4j/core/appender/FailoverAppenderTest.java  |  2 +-
 .../async/AsyncAppenderExceptionHandlingTest.java  | 97 ++++++++++++++++++++++
 .../core/config/AppenderControlArraySetTest.java   |  2 +-
 .../log4j/test/appender/FailOnceAppender.java      | 91 +++++++++++++++-----
 .../AsyncAppenderExceptionHandlingTest.xml         | 32 +++++++
 7 files changed, 219 insertions(+), 26 deletions(-)

diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/SneakyThrow.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/SneakyThrow.java
new file mode 100644
index 0000000..af26e6e
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/SneakyThrow.java
@@ -0,0 +1,13 @@
+package org.apache.logging.log4j.util;
+
+public enum SneakyThrow {;
+
+    /**
+     * Throws any exception (including checked ones!) without defining it in the method signature.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> void sneakyThrow(final Throwable throwable) throws E {
+        throw (E) throwable;
+    }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
index ee2f28f..1e71afa 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
@@ -154,10 +154,10 @@ public class AppenderControl extends AbstractFilterable {
     private void tryCallAppender(final LogEvent event) {
         try {
             appender.append(event);
-        } catch (final RuntimeException ex) {
-            handleAppenderError(event, ex);
-        } catch (final Exception ex) {
-            handleAppenderError(event, new AppenderLoggingException(ex));
+        } catch (final RuntimeException error) {
+            handleAppenderError(event, error);
+        } catch (final Throwable error) {
+            handleAppenderError(event, new AppenderLoggingException(error));
         }
     }
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java
index 54f8dbf..56c886d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java
@@ -77,7 +77,7 @@ public class FailoverAppenderTest {
         onceLogger.error("Second log message");
         events = app.getEvents();
         assertEquals(events.size(), 0, "Did not recover");
-        events = foApp.getEvents();
+        events = foApp.drainEvents();
         assertEquals(events.size(), 2, "Incorrect number of events in primary appender");
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java
new file mode 100644
index 0000000..8318c81
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.async;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AsyncAppender;
+import org.apache.logging.log4j.core.config.AppenderControl;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.test.appender.FailOnceAppender;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Verifies {@link AsyncAppender} works after certain type of {@link Appender}
+ * failures.
+ * <p>
+ * {@link AsyncAppender} thread is known to get killed due to
+ * {@link AppenderControl} leaking exceptions in the past. This class is more
+ * of an end-to-end test to verify that {@link AppenderControl} catches all kind
+ * of {@link Throwable}s.
+ */
+class AsyncAppenderExceptionHandlingTest {
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+            FailOnceAppender.ThrowableClassName.RUNTIME_EXCEPTION,
+            FailOnceAppender.ThrowableClassName.LOGGING_EXCEPTION,
+            FailOnceAppender.ThrowableClassName.EXCEPTION,
+            FailOnceAppender.ThrowableClassName.ERROR,
+            FailOnceAppender.ThrowableClassName.THROWABLE
+    })
+    void AsyncAppender_should_not_stop_on_appender_failures(String throwableClassName) {
+
+        // Create the logger.
+        final String throwableClassNamePropertyName = "throwableClassName";
+        System.setProperty(throwableClassNamePropertyName, throwableClassName);
+        try (final LoggerContext loggerContext =
+                     Configurator.initialize("Test", "AsyncAppenderExceptionHandlingTest.xml")) {
+            final Logger logger = loggerContext.getRootLogger();
+
+            // Log the 1st message, which should fail due to the FailOnceAppender.
+            logger.info("message #1");
+
+            // Log the 2nd message, which should succeed.
+            final String lastLogMessage = "message #2";
+            logger.info(lastLogMessage);
+
+            // Stop the AsyncAppender to drain the queued events.
+            Configuration configuration = loggerContext.getConfiguration();
+            AsyncAppender asyncAppender = configuration.getAppender("Async");
+            Assertions.assertNotNull(asyncAppender, "couldn't obtain the FailOnceAppender");
+            asyncAppender.stop();
+
+            // Verify the logged message.
+            final FailOnceAppender failOnceAppender = configuration.getAppender("FailOnce");
+            Assertions.assertNotNull(failOnceAppender, "couldn't obtain the FailOnceAppender");
+            Assertions.assertTrue(failOnceAppender.isFailed(), "FailOnceAppender hasn't failed yet");
+            final List<String> accumulatedMessages = failOnceAppender
+                    .drainEvents()
+                    .stream()
+                    .map(LogEvent::getMessage)
+                    .map(Message::getFormattedMessage)
+                    .collect(Collectors.toList());
+            Assertions.assertEquals(Collections.singletonList(lastLogMessage), accumulatedMessages);
+
+        } finally {
+            System.setProperty(throwableClassNamePropertyName, Strings.EMPTY);
+        }
+
+    }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
index e8469af..d102cfa 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
@@ -38,7 +38,7 @@ public class AppenderControlArraySetTest {
     }
 
     private AppenderControl createControl(final String name) {
-        final Appender appender = FailOnceAppender.createAppender(name);
+        final Appender appender = FailOnceAppender.createAppender(name, null);
         return new AppenderControl(appender, Level.INFO, null);
     }
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
index 2a18bc3..7fe0296 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
@@ -16,51 +16,102 @@
  */
 package org.apache.logging.log4j.test.appender;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.apache.logging.log4j.LoggingException;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.PluginAttribute;
-import org.apache.logging.log4j.plugins.PluginFactory;
 import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.SneakyThrow;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
 
 /**
- *
+ * An {@link Appender} that fails on the first use and works for the rest.
  */
 @Plugin(name="FailOnce", category ="Core", elementType=Appender.ELEMENT_TYPE, printObject=true)
 public class FailOnceAppender extends AbstractAppender {
 
-    boolean fail = true;
+    private final Supplier<Throwable> throwableSupplier;
 
-    private final List<LogEvent> events = new ArrayList<>();
+    private boolean failed = false;
 
-    private FailOnceAppender(final String name) {
-        super(name, null, null, false, null);
+    private List<LogEvent> events = new ArrayList<>();
+
+    private FailOnceAppender(final String name, final Supplier<Throwable> throwableSupplier) {
+        super(name, null, null, false, Property.EMPTY_ARRAY);
+        this.throwableSupplier = throwableSupplier;
     }
 
     @Override
-    public void append(final LogEvent event) {
-        if (fail) {
-            fail = false;
-            throw new LoggingException("Always fail");
+    public synchronized void append(final LogEvent event) {
+        if (!failed) {
+            failed = true;
+            Throwable throwable = throwableSupplier.get();
+            SneakyThrow.sneakyThrow(throwable);
         }
         events.add(event);
     }
 
-    public List<LogEvent> getEvents() {
-        final List<LogEvent> list = new ArrayList<>(events);
-        events.clear();
-        return list;
+    public synchronized boolean isFailed() {
+        return failed;
+    }
+
+    /**
+     * Returns the list of accumulated events and resets the internal buffer.
+     */
+    public synchronized List<LogEvent> drainEvents() {
+        final List<LogEvent> oldEvents = events;
+        this.events = new ArrayList<>();
+        return oldEvents;
     }
 
     @PluginFactory
     public static FailOnceAppender createAppender(
-        @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) {
-        return new FailOnceAppender(name);
+        @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name,
+        @PluginAttribute("throwableClassName") final String throwableClassName) {
+        final Supplier<Throwable> throwableSupplier = createThrowableSupplier(name, throwableClassName);
+        return new FailOnceAppender(name, throwableSupplier);
+    }
+
+    private static Supplier<Throwable> createThrowableSupplier(
+            final String name,
+            final String throwableClassName) {
+
+        // Fallback to LoggingException if none is given.
+        final String message = String.format("failing on purpose for appender '%s'", name);
+        if (throwableClassName == null || ThrowableClassName.LOGGING_EXCEPTION.equals(throwableClassName)) {
+            return () -> new LoggingException(message);
+        }
+
+        // Check against the expected exception classes.
+        switch (throwableClassName) {
+            case ThrowableClassName.RUNTIME_EXCEPTION: return () -> new RuntimeException(message);
+            case ThrowableClassName.EXCEPTION: return () -> new Exception(message);
+            case ThrowableClassName.ERROR: return () -> new Error(message);
+            case ThrowableClassName.THROWABLE: return () -> new Throwable(message);
+            default: throw new IllegalArgumentException("unknown throwable class name: " + throwableClassName);
+        }
+
+    }
+
+    public enum ThrowableClassName {;
+
+        public static final String RUNTIME_EXCEPTION = "RuntimeException";
+
+        public static final String LOGGING_EXCEPTION = "LoggingException";
+
+        public static final String EXCEPTION = "Exception";
+
+        public static final String ERROR = "Error";
+
+        public static final String THROWABLE = "Throwable";
+
     }
 
 }
diff --git a/log4j-core/src/test/resources/AsyncAppenderExceptionHandlingTest.xml b/log4j-core/src/test/resources/AsyncAppenderExceptionHandlingTest.xml
new file mode 100644
index 0000000..84764fe
--- /dev/null
+++ b/log4j-core/src/test/resources/AsyncAppenderExceptionHandlingTest.xml
@@ -0,0 +1,32 @@
+<?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="OFF" name="AsyncAppenderExceptionHandlingTest">
+  <Appenders>
+    <FailOnce name="FailOnce" throwableClassName="${sys:throwableClassName}"/>
+    <Async name="Async">
+      <AppenderRef ref="FailOnce" />
+    </Async>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="Async"/>
+    </Root>
+  </Loggers>
+</Configuration>


[logging-log4j2] 01/07: Try improving RollingDirectTimeNewDirectoryTest to alleviate test report failures.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a8d6e691558ba9bc4ebd4e5df104dbba06af3c05
Author: Volkan Yazıcı <vo...@gmail.com>
AuthorDate: Wed Aug 26 09:03:01 2020 +0200

    Try improving RollingDirectTimeNewDirectoryTest to alleviate test report failures.
---
 .../rolling/RollingDirectTimeNewDirectoryTest.java | 71 +++++++++++++++-------
 1 file changed, 49 insertions(+), 22 deletions(-)

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java
index 8372a7e..c1a2667 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java
@@ -16,42 +16,41 @@
  */
 package org.apache.logging.log4j.core.appender.rolling;
 
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.TrueFileFilter;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 
 import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-/**
- * Tests
- */
 public class RollingDirectTimeNewDirectoryTest {
+
     private static final String CONFIG = "log4j-rolling-folder-direct.xml";
 
+    // Note that the path is hardcoded in the configuration!
     private static final String DIR = "target/rolling-folder-direct";
 
-    public static LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
+    public static LoggerContextRule loggerContextRule =
+            LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
 
     @Rule
     public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR);
 
-    private Logger logger;
-
-    @Before
-    public void setUp() throws Exception {
-        this.logger = loggerContextRule.getLogger(RollingDirectTimeNewDirectoryTest.class.getName());
-    }
-
-
     @Test
     public void streamClosedError() throws Exception {
+
+        final Logger logger =
+                loggerContextRule.getLogger(
+                        RollingDirectTimeNewDirectoryTest.class.getName());
+
         for (int i = 0; i < 1000; i++) {
             logger.info("nHq6p9kgfvWfjzDRYbZp");
         }
@@ -60,14 +59,42 @@ public class RollingDirectTimeNewDirectoryTest {
             logger.info("nHq6p9kgfvWfjzDRYbZp");
         }
 
-        File tempDirectoryAsFile = new File(DIR);
-        File[] loggingFolders = tempDirectoryAsFile.listFiles();
-        assertNotNull(loggingFolders);
-        // Check if two folders were created
-        assertTrue("Not enough directories created", loggingFolders.length >= 2);
-        for (File dir : loggingFolders) {
-            File[] files = dir.listFiles();
-            assertTrue("No files in directory " + dir.toString(), files != null && files.length > 0);
+        File logDir = new File(DIR);
+        File[] logFolders = logDir.listFiles();
+        assertNotNull(logFolders);
+        Arrays.sort(logFolders);
+
+        try {
+
+            final int minExpectedLogFolderCount = 2;
+            assertTrue(
+                    "was expecting at least " + minExpectedLogFolderCount + " folders, " +
+                            "found " + logFolders.length,
+                    logFolders.length >= minExpectedLogFolderCount);
+
+            for (File logFolder : logFolders) {
+                File[] logFiles = logFolder.listFiles();
+                if (logFiles != null) {
+                    Arrays.sort(logFiles);
+                }
+                assertTrue("empty folder: " + logFolder, logFiles != null && logFiles.length > 0);
+            }
+
+        } catch (AssertionError error) {
+            System.out.format("log directory (%s) contents:%n", DIR);
+            final Iterator<File> fileIterator =
+                    FileUtils.iterateFilesAndDirs(
+                            logDir, TrueFileFilter.TRUE, TrueFileFilter.TRUE);
+            int totalFileCount = 0;
+            while (fileIterator.hasNext()) {
+                totalFileCount++;
+                final File file = fileIterator.next();
+                System.out.format("-> %s (%d)%n", file, file.length());
+            }
+            System.out.format("total file count: %d%n", totalFileCount);
+            throw new AssertionError("check failure", error);
         }
+
     }
+
 }


[logging-log4j2] 04/07: LOG4J2-620 ReconfigurationDeadlockTest rewrite to prevent spurious failures.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 80f62c4c7b78fca08d82fd648efe69c88ffd487f
Author: Volkan Yazıcı <vo...@gmail.com>
AuthorDate: Tue Oct 13 11:21:38 2020 +0200

    LOG4J2-620 ReconfigurationDeadlockTest rewrite to prevent spurious failures.
---
 .../core/config/ReconfigurationDeadlockTest.java   | 209 +++++++++++++--------
 .../log4j/core/config/xml/XmlSchemaTest.java       |   2 +-
 .../log4j/test/SomethingThatUsesLogging.java       |  36 ----
 .../log4j/test/appender/DeadlockAppender.java      |  84 ---------
 .../log4j/test/appender/UsesLoggingAppender.java   |  67 -------
 .../test/resources/reconfiguration-deadlock.xml    |  25 ++-
 6 files changed, 144 insertions(+), 279 deletions(-)

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java
index 5a6d5a2..399e3e5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java
@@ -16,107 +16,162 @@
  */
 package org.apache.logging.log4j.core.config;
 
-import java.io.File;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import org.apache.logging.log4j.junit.LoggerContextSource;
-import org.apache.logging.log4j.message.ThreadDumpMessage;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.RepeatedTest;
 
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Performs reconfiguration whilst logging.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-620">LOG4J2-620</a>
+ * @see TestAppender
+ */
 @LoggerContextSource("reconfiguration-deadlock.xml")
 public class ReconfigurationDeadlockTest {
 
-    private static final int THREAD_COUNT = 5;
-    private static final boolean[] finished = new boolean[THREAD_COUNT];
-    private static LoggerThread[] threads = new LoggerThread[THREAD_COUNT];
-
-    @Test
-    public void testReconfig() throws InterruptedException {
+    private static final int WORKER_COUNT = 100;
 
-        final Updater updater = new Updater();
-        for (int i = 0; i < THREAD_COUNT; ++i) {
-            threads[i] = new LoggerThread(i);
-            threads[i].setDaemon(true);
-        }
-        for (int i = 0; i < THREAD_COUNT; ++i) {
+    private ExecutorService executor;
 
-            threads[i].start();
-        }
-        updater.setDaemon(true);
-        updater.start();
-        Thread.sleep(100);
-        boolean stillWaiting = true;
-        for (int i = 0; i < 200; ++i) {
-            int index = 0;
-            for (; index < THREAD_COUNT; ++index) {
-                if (!finished[index]) {
-                    break;
-                }
-            }
-            if (index == THREAD_COUNT) {
-                stillWaiting = false;
-                break;
-            }
-            Thread.sleep(100);
-        }
-        updater.shutdown = true;
-        if (stillWaiting) {
-            final ThreadDumpMessage message = new ThreadDumpMessage("Waiting");
-            System.err.print(message.getFormattedMessage());
-        }
-        for (int i = 0; i < THREAD_COUNT; ++i) {
-            if (threads[i].isAlive()) {
-                threads[i].interrupt();
-            }
-        }
-        assertFalse(stillWaiting, "loggerThread didn't finish");
+    @BeforeEach
+    public void startExecutor() {
+        executor = Executors.newFixedThreadPool(WORKER_COUNT);
+    }
 
+    @AfterEach
+    public void stopExecutor() throws InterruptedException {
+        executor.shutdownNow();
+        final boolean terminated = executor.awaitTermination(30, TimeUnit.SECONDS);
+        Assertions.assertTrue(terminated, "couldn't terminate the executor");
     }
 
-    private static class LoggerThread extends Thread {
+    @RepeatedTest(100)
+    public void reconfiguration_should_not_cause_deadlock_for_ongoing_logging() throws Exception {
 
-        private final Logger logger = LogManager.getRootLogger();
-        private final int index;
+        // Try to update the config file to ensure that we can indeed update it.
+        updateConfigFileModTime();
 
-        public LoggerThread(final int i) {
-            index = i;
-        }
-        @Override
-        public void run() {
-            int i = 0;
+        // Start the workers.
+        final CountDownLatch workerStartLatch = new CountDownLatch(WORKER_COUNT);
+        List<Future<?>> workerFutures = initiateWorkers(workerStartLatch, executor);
+
+        // Await workers to start and update the config file.
+        workerStartLatch.await(10, TimeUnit.SECONDS);
+        updateConfigFileModTime();
+
+        // Verify that all workers have finished okay.
+        for (int workerIndex = 0; workerIndex < WORKER_COUNT; workerIndex++) {
+            final Future<?> workerFuture = workerFutures.get(workerIndex);
             try {
-                for (i=0; i < 30; ++i) {
-                    logger.error("Thread: " + index + ", Test: " + i++);
-                }
-            } catch (final Exception ie) {
-                return;
+                Object workerResult = workerFuture.get(30, TimeUnit.SECONDS);
+                Assertions.assertNull(workerResult);
+            } catch (final Throwable failure) {
+                final String message = String.format(
+                        "check for worker %02d/%02d has failed",
+                        (workerIndex + 1), WORKER_COUNT);
+                throw new AssertionError(message, failure);
             }
-            finished[index] = true;
         }
+
     }
 
-    private static class Updater extends Thread {
+    private static void updateConfigFileModTime() {
+        final File file = new File("target/test-classes/reconfiguration-deadlock.xml");
+        final boolean fileModified = file.setLastModified(System.currentTimeMillis());
+        Assertions.assertTrue(fileModified, "couldn't update file modification time");
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    private static List<Future<?>> initiateWorkers(
+            final CountDownLatch workerStartLatch,
+            final ExecutorService executor) {
+        final Logger logger = LogManager.getRootLogger();
+        return IntStream
+                .range(0, WORKER_COUNT)
+                .mapToObj((final int workerIndex) -> executor.submit(() -> {
+                    int i = 0;
+                    for (; i < 1_000; i++) {
+                        logger.error("worker={}, iteration={}", workerIndex, i);
+                    }
+                    workerStartLatch.countDown();
+                    for (; i < 5_000; i++) {
+                        logger.error("worker={}, iteration={}", workerIndex, i);
+                    }
+                }))
+                .collect(Collectors.toList());
+    }
 
-        public volatile boolean shutdown = false;
+    /**
+     * A dummy appender doing nothing but burning CPU cycles whilst randomly accessing the logger.
+     */
+    @Plugin(name = "ReconfigurationDeadlockTestAppender",
+            category = Core.CATEGORY_NAME,
+            elementType = Appender.ELEMENT_TYPE,
+            printObject = true)
+    public static final class TestAppender extends AbstractAppender {
+
+        private final Logger logger;
+
+        private TestAppender(
+                final String name,
+                final Filter filter,
+                final Layout<?> layout,
+                final boolean ignoreExceptions) {
+            super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
+            this.logger = LogManager.getRootLogger();
+        }
 
+        @PluginFactory
+        public static TestAppender createAppender(
+                @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name,
+                @PluginAttribute("ignoreExceptions") final boolean ignore,
+                @PluginElement("Layout") final Layout<?> layout,
+                @PluginElement("Filter") final Filter filter) {
+            return new TestAppender(name, filter, layout, ignore);
+        }
+
+        /**
+         * Does nothing but burning CPU cycles and accessing to the logger.
+         */
         @Override
-        public void run() {
-            while (!shutdown) {
-                try {
-                    Thread.sleep(1000);
-                } catch (final InterruptedException e) {
-                    e.printStackTrace();
-                }
-                // for running from IDE
-                final File file = new File("target/test-classes/reconfiguration-deadlock.xml");
-                if (file.exists()) {
-                    file.setLastModified(System.currentTimeMillis());
-                }
+        public void append(final LogEvent event) {
+            boolean endOfBatch;
+            final int eventHashCode = event.hashCode();
+            switch (Math.abs(eventHashCode % 4)) {
+                case 0: endOfBatch = logger.isTraceEnabled(); break;
+                case 1: endOfBatch = logger.isDebugEnabled(); break;
+                case 2: endOfBatch = logger.isInfoEnabled(); break;
+                case 3: endOfBatch = logger.isWarnEnabled(); break;
+                default: throw new IllegalStateException();
             }
+            event.setEndOfBatch(endOfBatch);
         }
+
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java
index 35f2367..21a997d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java
@@ -59,7 +59,7 @@ public class XmlSchemaTest {
             "log4j-xinclude", //
             "log4j12", // log4j 1.x configs
             "perf-CountingNoOpAppender.xml", // uses test-appender CountingNoOp
-            "reconfiguration-deadlock.xml", // uses test-appender UsesLoggingAppender
+            "reconfiguration-deadlock.xml", // uses test-appender ReconfigurationDeadlockTestAppender
             "XmlConfigurationSecurity.xml" //
     );
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/SomethingThatUsesLogging.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/SomethingThatUsesLogging.java
deleted file mode 100644
index 11715fd..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/SomethingThatUsesLogging.java
+++ /dev/null
@@ -1,36 +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.test;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-/**
- *
- */
-public class SomethingThatUsesLogging {
-
-    private final Logger logger;
-
-    public SomethingThatUsesLogging() {
-        logger = LogManager.getLogger();
-    }
-
-    public void doSomething() {
-        logger.isEnabled(Level.DEBUG);
-    }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
deleted file mode 100644
index 9afd483..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
+++ /dev/null
@@ -1,84 +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.test.appender;
-
-import java.util.concurrent.TimeUnit;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.LoggingException;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.PluginAttribute;
-import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.plugins.validation.constraints.Required;
-
-/**
- *
- */
-@Plugin(name="Deadlock", category ="Core", elementType=Appender.ELEMENT_TYPE, printObject=true)
-public class DeadlockAppender extends AbstractAppender {
-
-    private WorkerThread thread = null;
-
-    private DeadlockAppender(final String name) {
-        super(name, null, null, false, null);
-        thread = new WorkerThread();
-    }
-
-    @Override
-    public void start() {
-        super.start();
-
-    }
-
-    @Override
-    public boolean stop(final long timeout, final TimeUnit timeUnit) {
-        setStopping();
-        super.stop(timeout, timeUnit, false);
-        thread.start();
-        try {
-            thread.join();
-        } catch (final Exception ex) {
-            System.out.println("Thread interrupted");
-        }
-        setStopped();
-        return true;
-    }
-
-    @Override
-    public void append(final LogEvent event) {
-        throw new LoggingException("Always fail");
-    }
-
-    @PluginFactory
-    public static DeadlockAppender createAppender(
-        @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) {
-        return new DeadlockAppender(name);
-    }
-
-    private class WorkerThread extends Thread {
-
-        @Override
-        public void run() {
-            final Logger logger = LogManager.getLogger("org.apache.logging.log4j.test.WorkerThread");
-            logger.debug("Worker is running");
-        }
-    }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
deleted file mode 100644
index 9b4abfd..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
+++ /dev/null
@@ -1,67 +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.test.appender;
-
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.PluginAttribute;
-import org.apache.logging.log4j.plugins.PluginElement;
-import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.test.SomethingThatUsesLogging;
-
-/**
- *
- */
-@Plugin(name = "UsesLoggingAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
-public final class UsesLoggingAppender extends AbstractAppender {
-
-    private final SomethingThatUsesLogging thing;
-
-    private UsesLoggingAppender(final String name, final Filter filter, final Layout<?> layout, final boolean ignoreExceptions) {
-        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
-        thing = new SomethingThatUsesLogging();
-    }
-
-    @PluginFactory
-    public static UsesLoggingAppender createAppender(
-        @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name,
-        @PluginAttribute("ignoreExceptions") final boolean ignore,
-        @PluginElement final Layout<?> layout,
-        @PluginElement final Filter filter) {
-        return new UsesLoggingAppender(name, filter, layout, ignore);
-    }
-
-    @Override
-    public void append(final LogEvent event) {
-        try {
-            for (int i = 0; i < 50; i++) {
-                Thread.sleep(10);
-                thing.doSomething();
-            }
-        } catch (final InterruptedException e) {
-            e.printStackTrace();
-        }
-        // System.out.print("Log: " + getLayout().toSerializable(event));
-    }
-}
diff --git a/log4j-core/src/test/resources/reconfiguration-deadlock.xml b/log4j-core/src/test/resources/reconfiguration-deadlock.xml
index 5ef897c..d313a4e 100644
--- a/log4j-core/src/test/resources/reconfiguration-deadlock.xml
+++ b/log4j-core/src/test/resources/reconfiguration-deadlock.xml
@@ -1,19 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="error" monitorInterval="5">
-  <Appenders>
-    <UsesLoggingAppender name="myAppender">
+<configuration status="error" monitorInterval="5">
+  <appenders>
+    <ReconfigurationDeadlockTestAppender name="testAppender">
       <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
-    </UsesLoggingAppender>
+    </ReconfigurationDeadlockTestAppender>
     <Console name="STDOUT">
       <PatternLayout pattern="%m%n"/>
     </Console>
-  </Appenders>
-  <Loggers>
-    <Logger name="Dump" level="trace" additivity="false">
-      <AppenderRef ref="STDOUT" />
-    </Logger>
-    <Root level="ERROR">
-      <AppenderRef ref="myAppender" />
-    </Root>
-  </Loggers>
-</Configuration>
\ No newline at end of file
+  </appenders>
+  <loggers>
+    <root level="ERROR">
+      <appenderRef ref="testAppender" />
+    </root>
+  </loggers>
+</configuration>


[logging-log4j2] 05/07: Delete unused BasicLayout.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5c938e16ab668490e918088c6f49a71bf46a76dc
Author: Volkan Yazıcı <vo...@gmail.com>
AuthorDate: Tue Oct 13 11:21:55 2020 +0200

    Delete unused BasicLayout.
---
 .../logging/log4j/test/layout/BasicLayout.java     | 57 ----------------------
 1 file changed, 57 deletions(-)

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
deleted file mode 100644
index eeae653..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
+++ /dev/null
@@ -1,57 +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.test.layout;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.core.layout.AbstractStringLayout;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- *
- */
-@Plugin(name = "BasicLayout", category = Core.CATEGORY_NAME, elementType = "layout", printObject = true)
-public class BasicLayout extends AbstractStringLayout {
-
-    private static final String HEADER = "Header" + Strings.LINE_SEPARATOR;
-
-    public BasicLayout(final Charset charset) {
-        super(charset);
-    }
-
-    @Override
-    public byte[] getHeader() {
-        return getBytes(HEADER);
-    }
-
-    @Override
-    public String toSerializable(final LogEvent event) {
-        return event.getMessage().getFormattedMessage() + Strings.LINE_SEPARATOR;
-    }
-
-    /**
-     */
-    @PluginFactory
-    public static BasicLayout createLayout() {
-        return new BasicLayout(StandardCharsets.UTF_8);
-    }
-}


[logging-log4j2] 02/07: Upload test reports in GitHub Actions.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f5d8780ef641a6ae61280ef3978d3bb9be922614
Author: Volkan Yazıcı <vo...@gmail.com>
AuthorDate: Wed Aug 26 14:34:31 2020 +0200

    Upload test reports in GitHub Actions.
---
 .github/workflows/maven.yml | 27 ++++++++-------------------
 1 file changed, 8 insertions(+), 19 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 5647d10..5bdb2f7 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -46,14 +46,6 @@ jobs:
         if: runner.os == 'Linux'
         run: ./mvnw -V -B -e -DtrimStackTrace=false -Dmaven.test.failure.ignore=true -Dsurefire.rerunFailingTestsCount=1 --global-toolchains .github/workflows/maven-toolchains.xml verify
 
-      - name: Publish Test Results (Linux)
-        if: runner.os == 'Linux'
-        uses: scacap/action-surefire-report@v1
-        with:
-          github_token: ${{ secrets.GITHUB_TOKEN }}
-          check_name: 'Test Report (Linux)'
-          report_paths: '**/*-reports/TEST-*.xml'
-
       - name: Inspect environment (Windows)
         if: runner.os == 'Windows'
         run: set java
@@ -62,14 +54,6 @@ jobs:
         if: runner.os == 'Windows'
         run: ./mvnw -V -B -e -DtrimStackTrace=false "-Dmaven.test.failure.ignore=true" "-Dsurefire.rerunFailingTestsCount=1" --global-toolchains ".github\workflows\maven-toolchains.xml" verify
 
-      - name: Publish Test Results (Windows)
-        if: runner.os == 'Windows'
-        uses: scacap/action-surefire-report@v1
-        with:
-          github_token: ${{ secrets.GITHUB_TOKEN }}
-          check_name: 'Test Report (Windows)'
-          report_paths: '**/*-reports/TEST-*.xml'
-
       - name: Inspect environment (MacOS)
         if: runner.os == 'macOS'
         run: env | grep '^JAVA'
@@ -78,10 +62,15 @@ jobs:
         if: runner.os == 'macOS'
         run: ./mvnw -V -B -e -DtrimStackTrace=false -Dmaven.test.failure.ignore=true -Dsurefire.rerunFailingTestsCount=1 --global-toolchains .github/workflows/maven-toolchains.xml verify
 
-      - name: Publish Test Results (MacOS)
-        if: runner.os == 'macOS'
+      - name: Publish Test Results
         uses: scacap/action-surefire-report@v1
         with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
-          check_name: 'Test Report (MacOS)'
+          check_name: Test Report (${{ matrix.os }})
           report_paths: '**/*-reports/TEST-*.xml'
+
+      - name: Upload Test Reports
+        uses: actions/upload-artifact@v2
+        with:
+          name: test-reports-${{ matrix.os }}
+          path: '**/*-reports'


[logging-log4j2] 07/07: LOG4J2-2936 Add message parameter resolver to JSON template layout.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 51278468e49ff1b4548225a6a38f060061694547
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Fri Nov 6 13:30:39 2020 +0100

    LOG4J2-2936 Add message parameter resolver to JSON template layout.
---
 .../json/resolver/EventResolverFactories.java      |   1 +
 .../template/json/resolver/LevelResolver.java      |   2 +-
 .../json/resolver/MessageParameterResolver.java    | 131 +++++++++++++++++++++
 .../resolver/MessageParameterResolverFactory.java  |  41 +++++++
 .../template/json/JsonTemplateLayoutTest.java      |  51 ++++++++
 .../asciidoc/manual/json-template-layout.adoc.vm   |  52 ++++++++
 6 files changed, 277 insertions(+), 1 deletion(-)

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 07fe97b..b4b4f49 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
@@ -44,6 +44,7 @@ enum EventResolverFactories {;
                 MapResolverFactory.getInstance(),
                 MarkerResolverFactory.getInstance(),
                 MessageResolverFactory.getInstance(),
+                MessageParameterResolverFactory.getInstance(),
                 PatternResolverFactory.getInstance(),
                 SourceResolverFactory.getInstance(),
                 ThreadResolverFactory.getInstance(),
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 89de7e0..2952eb4 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
@@ -27,7 +27,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
- * Level resolver.
+ * {@link Level} resolver.
  *
  * <h3>Configuration</h3>
  *
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
new file mode 100644
index 0000000..a21dff6
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.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.core.LogEvent;
+import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+import org.apache.logging.log4j.message.Message;
+
+/**
+ * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config      = [ stringified ] , [ index ]
+ * stringified = "stringified" -> boolean
+ * index       = "index" -> number
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the message parameters into an array:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "messageParameter"
+ * }
+ * </pre>
+ *
+ * Resolve the string representation of all message parameters into an array:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "messageParameter",
+ *   "stringified": true
+ * }
+ * </pre>
+ *
+ * Resolve the first message parameter:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "messageParameter",
+ *   "index": 0
+ * }
+ *
+ * Resolve the string representation of the first message parameter:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "messageParameter",
+ *   "index": 0,
+ *   "stringified": true
+ * }
+ * </pre>
+ */
+final class MessageParameterResolver implements EventResolver {
+
+    private final boolean stringified;
+
+    private final int index;
+
+    MessageParameterResolver(final TemplateResolverConfig config) {
+        this.stringified = config.getBoolean("stringified", false);
+        final Integer index = config.getInteger("index");
+        if (index != null && index < 0) {
+            throw new IllegalArgumentException("was expecting a positive index: " + config);
+        }
+        this.index = index == null ? -1 : index;
+    }
+
+    static String getName() {
+        return "messageParameter";
+    }
+
+    @Override
+    public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
+
+        // Short-circuit if there are no parameters.
+        final Object[] parameters = logEvent.getMessage().getParameters();
+        if (parameters.length == 0) {
+            jsonWriter.writeNull();
+            return;
+        }
+
+        // Resolve all parameters.
+        if (index < 0) {
+            jsonWriter.writeArrayStart();
+            for (int i = 0; i < parameters.length; i++) {
+                if (i > 0) {
+                    jsonWriter.writeSeparator();
+                }
+                final Object parameter = parameters[i];
+                if (stringified) {
+                    final String stringifiedParameter = String.valueOf(parameter);
+                    jsonWriter.writeString(stringifiedParameter);
+                } else {
+                    jsonWriter.writeValue(parameter);
+                }
+            }
+            jsonWriter.writeArrayEnd();
+        }
+
+        // Resolve a single parameter.
+        else {
+            final Object parameter = parameters[index];
+            if (stringified) {
+                final String stringifiedParameter = String.valueOf(parameter);
+                jsonWriter.writeString(stringifiedParameter);
+            } else {
+                jsonWriter.writeValue(parameter);
+            }
+        }
+
+    }
+
+}
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
new file mode 100644
index 0000000..db1c369
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolverFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+final class MessageParameterResolverFactory implements EventResolverFactory<MessageParameterResolver> {
+
+    private static final MessageParameterResolverFactory INSTANCE = new MessageParameterResolverFactory();
+
+    private MessageParameterResolverFactory() {}
+
+    static MessageParameterResolverFactory getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public String getName() {
+        return MessageParameterResolver.getName();
+    }
+
+    @Override
+    public MessageParameterResolver create(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        return new MessageParameterResolver(config);
+    }
+
+}
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 9de4d87..782db2d 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
@@ -40,6 +40,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.layout.template.json.util.MapAccessor;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.StringMapMessage;
 import org.apache.logging.log4j.test.AvailablePortFinder;
@@ -1786,6 +1787,56 @@ public class JsonTemplateLayoutTest {
     }
 
     @Test
+    public void test_MessageParameterResolver() {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(Map(
+                "po*", Map(
+                        "$resolver", "messageParameter"),
+                "ps*", Map(
+                        "$resolver", "messageParameter",
+                        "stringified", true),
+                "po2", Map(
+                        "$resolver", "messageParameter",
+                        "index", 2),
+                "ps2", Map(
+                        "$resolver", "messageParameter",
+                        "index", 2,
+                        "stringified", true)));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event.
+        final Object[] parameters = {1L + (long) Integer.MAX_VALUE, "foo", 56};
+        final Message message = new ParameterizedMessage("foo", parameters);
+        final Level level = Level.FATAL;
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setLoggerName(LOGGER_NAME)
+                .setMessage(message)
+                .setLevel(level)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getObject("po*")).isEqualTo(Arrays.asList(parameters));
+            List<String> stringifiedParameters = Arrays
+                    .stream(parameters)
+                    .map(String::valueOf)
+                    .collect(Collectors.toList());
+            assertThat(accessor.getObject("ps*")).isEqualTo(stringifiedParameters);
+            assertThat(accessor.getObject("po2")).isEqualTo(parameters[2]);
+            assertThat(accessor.getString("ps2")).isEqualTo(stringifiedParameters.get(2));
+        });
+
+    }
+
+    @Test
     public void test_unresolvable_nested_fields_are_skipped() {
 
         // Create the event template.
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 1c370ca..2bc7e44 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -804,6 +804,58 @@ Using this configuration, a `SimpleMessage` will generate a
 `{"action": "login", "sessionId": "87asd97a"}`. Note that both emitted JSONs are
 of type `object` and have no type-conflicting fields.
 
+| messageParameter
+a|
+[source]
+----
+config      = [ stringified ] , [ index ]
+stringified = "stringified" -> boolean
+index       = "index" -> number
+----
+| `logEvent.getMessage().getParameters()`
+| `stringified` flag translates to `String.valueOf(value)`, hence mind
+  not-`String`-typed values.
+a|
+Resolve the message parameters into an array:
+
+[source,json]
+----
+{
+  "$resolver": "messageParameter"
+}
+----
+
+Resolve the string representation of all message parameters into an array:
+
+[source,json]
+----
+{
+  "$resolver": "messageParameter",
+  "stringified": true
+}
+----
+
+Resolve the first message parameter:
+
+[source,json]
+----
+{
+  "$resolver": "messageParameter",
+  "index": 0
+}
+----
+
+Resolve the string representation of the first message parameter:
+
+[source,json]
+----
+{
+  "$resolver": "messageParameter",
+  "index": 0,
+  "stringified": true
+}
+----
+
 | ndc
 a|
 [source]