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-log4j-tools.git


The following commit(s) were added to refs/heads/master by this push:
     new d5fd695  Added `log4j-changelog:export-single` (#38)
d5fd695 is described below

commit d5fd695d39017e89ed83a620fd5dacf5da473727
Author: Volkan Yazıcı <[email protected]>
AuthorDate: Fri Mar 3 18:28:46 2023 +0100

    Added `log4j-changelog:export-single` (#38)
---
 CHANGELOG.adoc                                     |  4 ++
 log4j-changelog-maven-plugin/README.adoc           | 75 +++++++++++++++-------
 log4j-changelog-maven-plugin/pom.xml               |  6 ++
 .../java/org/apache/logging/log4j/ExportMojo.java  |  4 +-
 .../{ExportMojo.java => ExportSingleMojo.java}     | 36 ++++++++---
 .../log4j/{ReleaseMojo.java => ImportMojo.java}    | 36 +++++++----
 .../java/org/apache/logging/log4j/ReleaseMojo.java |  3 +-
 log4j-changelog/README.adoc                        | 24 +++----
 .../logging/log4j/changelog/ChangelogFiles.java    |  7 --
 .../changelog/exporter/ChangelogExporter.java      | 70 ++++++++++++++------
 .../changelog/exporter/ChangelogExporterArgs.java  | 73 ++++++++++++++++++---
 .../changelog/importer/MavenChangesImporter.java   |  7 +-
 .../importer/MavenChangesImporterArgs.java         | 10 ---
 .../changelog/releaser/ChangelogReleaser.java      |  7 +-
 .../changelog/releaser/ChangelogReleaserArgs.java  |  9 ---
 .../log4j/changelog/util/PropertyUtils.java        | 61 ------------------
 .../log4j/changelog/ChangelogExporterTest.java     | 19 +++++-
 .../resources/3-enriched/.changelog-email.txt.ftl  | 40 ++++++++++++
 .../3-enriched/.changelog-entries.adoc.ftl         |  2 +-
 .../{4-exported => 4-exported-multi}/2.17.2.adoc   |  4 +-
 .../{4-exported => 4-exported-multi}/2.18.0.adoc   |  6 +-
 .../{4-exported => 4-exported-multi}/2.x.x.adoc    |  4 +-
 .../{4-exported => 4-exported-multi}/3.x.x.adoc    |  4 +-
 .../{4-exported => 4-exported-multi}/4.x.x.adoc    |  0
 .../{4-exported => 4-exported-multi}/index.adoc    |  0
 .../4-exported-single/.changelog-email.txt         | 27 ++++++++
 .../resources/5-released/.changelog-email.txt.ftl  | 40 ++++++++++++
 .../5-released/.changelog-entries.adoc.ftl         |  2 +-
 log4j-tools-parent/pom.xml                         |  2 +
 29 files changed, 378 insertions(+), 204 deletions(-)

diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index ca675b3..f382273 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -17,6 +17,10 @@ limitations under the License.
 
 == 0.x.x
 
+* Removed the default lifecycle from `log4j-changelog:release` Maven goal
+
+* Added 
xref:log4j-changelog-maven-plugin/README.adoc#export-single[`log4j-changelog:export-single`
 Maven goal] to export a single release directory against a custom template 
(for https://github.com/apache/logging-log4j-tools/issues/38[#38] by Volkan 
Yazıcı)
+
 * The changelog release models passed to the index template file 
(`.index.adoc.ftl`) is enriched with `changelogEntryCount` fields (for 
https://github.com/apache/logging-log4j-tools/issues/37[#37] by Volkan Yazıcı)
 
 == 0.2.0 (2023-01-31)
diff --git a/log4j-changelog-maven-plugin/README.adoc 
b/log4j-changelog-maven-plugin/README.adoc
index ee9b580..88bb6dd 100644
--- a/log4j-changelog-maven-plugin/README.adoc
+++ b/log4j-changelog-maven-plugin/README.adoc
@@ -17,31 +17,21 @@ limitations under the License.
 
 This project ships a Maven plugin providing convenient access to the 
`ChangelogExporter` and `ChangelogReleaser` of 
xref:../log4j-changelog/README.adoc[log4j-changelog].
 
-== Exporting changelogs to AsciiDoc files
+[#export]
+== Exporting changelogs
 
-You can use the `export` goal wrapping 
xref:../log4j-changelog/README.adoc#qa-generate[`ChangelogExporter` to generate 
AsciiDoc files from a changelog directory].
+You can use the `export` goal to export a changelog directory (e.g., 
`src/changelog`) using provided templates (i.e., `.index.adoc.ftl` and 
`.changelog.adoc.ftl`) and generate `index.adoc`, `2.18.0.adoc`, `2.19.0.adoc`, 
etc.
 An example usage is shared below.
 
-.`pom.xml` snippet that goes into the `project > build > plugins` block
-[source,xml]
+.Export AsciiDoc-formatted release notes to 
`target/generated-sources/site/asciidoc/changelog`
+[source,bash]
 ----
-<!-- export AsciiDoc-formatted sources to 
`target/generated-sources/site/asciidoc/changelog` -->
-<plugin>
-  <groupId>org.apache.logging.log4j</groupId>
-  <artifactId>log4j-changelog-maven-plugin</artifactId>
-  <inherited>false</inherited>
-  <executions>
-    <execution>
-      <id>generate-changelog</id>
-      <goals>
-        <goal>export</goal>
-      </goals>
-    </execution>
-  </executions>
-</plugin>
+./mvnw -N log4j-changelog:export
 ----
 
-`export` goal by defaults runs during the `pre-site` phase and accepts the 
following configuration parameters:
+Note that above we are using `-N` (`--non-recursive`) to avoid visiting 
submodules, which also makes the run faster.
+
+`export` goal by default runs during the `pre-site` phase and accepts the 
following configuration parameters:
 
 `changelogDirectory`::
 Directory containing release folders composed of changelog entry XML files.
@@ -51,18 +41,59 @@ It defaults to `${project.basedir}/src/changelog` and can 
be set using the `log4
 Directory to write generated changelog files.
 It defaults to 
`${project.build.directory}/generated-sources/site/asciidoc/changelog` and can 
be set using the `log4j.changelog.exporter.outputDirectory` property.
 
+[#export-single]
+== Exporting a release changelog directory
+
+It is pretty common to export a single release changelog directory using a 
particular template.
+For instance, to export `src/changelog/2.19.0` using 
`src/changelog/.changelog-email.txt.ftl` to `/tmp/changelog-email.txt`.
+You can use the `export-single` goal for this purpose.
+
+An example usage is shared below.
+
+.Export email-friendly release notes for version 2.19.0 to 
`/tmp/release-notes-email.txt`
+[source,bash]
+----
+./mvnw -N log4j-changelog:export-single \
+    -Dlog4j.changelog.releaseVersion=2.19.0 \
+    -Dlog4j.changelog.templateFile=src/changelog/.changelog-email.txt.ftl \
+    -Dlog4j.changelog.outputFile=/tmp/release-notes-email.txt
+----
+
+Note that above we are using `-N` (`--non-recursive`) to avoid visiting 
submodules, which also makes the run faster.
+
+`export-single` goal does not have a default phase and accepts the following 
configuration parameters:
+
+`changelogDirectory`::
+Directory containing release folders composed of changelog entry XML files.
+It defaults to `${project.basedir}/src/changelog` and can be set using the 
`log4j.changelog.directory` property.
+
+`releaseVersion`::
+The version of the release to populate changelog entries from.
+It can be set using the `log4j.changelog.releaseVersion` property.
+
+`templateFile`::
+The FreeMarker template file, e.g., `.changelog-email.txt.ftl`.
+It can be set using the `log4j.changelog.templateFile` property.
+
+`outputFile`::
+The output file, e.g., `changelog-email.txt`.
+It can be set using the `log4j.changelog.outputFile` property.
+
+[#release]
 == Populating a release changelog directory
 
 You can use the `release` goal wrapping 
xref:../log4j-changelog/README.adoc#qa-deploy-release[`ChangelogReleaser` to 
populate a release changelog directory].
 An example usage is shared below.
 
+.Populate `src/changelog/2.19.0` from `src/changelog/.2.x.x`
 [source,bash]
 ----
-# Populate `src/changelog/<releaseVersion>` from 
`src/changelog/.<releaseVersionMajor>.x.x`
-./mvnw -N log4j-changelog:releaser -Dlog4j.changelog.releaseVersion=2.19.0
+./mvnw -N log4j-changelog:release -Dlog4j.changelog.releaseVersion=2.19.0
 ----
 
-`release` goal by defaults runs during the `validate` phase and accepts the 
following configuration parameters:
+Note that above we are using `-N` (`--non-recursive`) to avoid visiting 
submodules, which also makes the run faster.
+
+`release` goal does not have default phase and accepts the following 
configuration parameters:
 
 `changelogDirectory`::
 Directory containing release folders composed of changelog entry XML files.
diff --git a/log4j-changelog-maven-plugin/pom.xml 
b/log4j-changelog-maven-plugin/pom.xml
index 21fd474..2a3ada3 100644
--- a/log4j-changelog-maven-plugin/pom.xml
+++ b/log4j-changelog-maven-plugin/pom.xml
@@ -38,6 +38,12 @@
       <artifactId>log4j-changelog</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>com.github.spotbugs</groupId>
+      <artifactId>spotbugs-annotations</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.maven.plugin-tools</groupId>
       <artifactId>maven-plugin-annotations</artifactId>
diff --git 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
index ab2cc77..2205c22 100644
--- 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
+++ 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
@@ -53,9 +53,7 @@ public class ExportMojo extends AbstractMojo {
     private File outputDirectory;
 
     public void execute() {
-        ChangelogExporterArgs args = new ChangelogExporterArgs(
-                changelogDirectory.toPath(),
-                outputDirectory.toPath());
+        ChangelogExporterArgs args = 
ChangelogExporterArgs.of(changelogDirectory.toPath(), outputDirectory.toPath());
         ChangelogExporter.performExport(args);
     }
 
diff --git 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportSingleMojo.java
similarity index 63%
copy from 
log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
copy to 
log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportSingleMojo.java
index ab2cc77..3d16d03 100644
--- 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportMojo.java
+++ 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ExportSingleMojo.java
@@ -22,17 +22,16 @@ import 
org.apache.logging.log4j.changelog.exporter.ChangelogExporter;
 import org.apache.logging.log4j.changelog.exporter.ChangelogExporterArgs;
 
 import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
 /**
- * Goal exporting the changelog directory.
+ * Goal exporting a release changelog directory against a single template.
  *
  * @see ChangelogExporter
  */
-@Mojo(name = "export", defaultPhase = LifecyclePhase.PRE_SITE)
-public class ExportMojo extends AbstractMojo {
+@Mojo(name = "export-single")
+public class ExportSingleMojo extends AbstractMojo {
 
     /**
      * Directory containing release folders composed of changelog entry XML 
files.
@@ -44,18 +43,35 @@ public class ExportMojo extends AbstractMojo {
     private File changelogDirectory;
 
     /**
-     * Directory to write generated changelog files.
+     * The version to be released, e.g., {@code 2.19.0}.
      */
     @Parameter(
-            defaultValue = 
"${project.build.directory}/generated-sources/site/asciidoc/changelog",
-            property = ChangelogExporterArgs.OUTPUT_DIRECTORY_PROPERTY_NAME,
+            property = ChangelogExporterArgs.RELEASE_VERSION_PROPERTY_NAME,
             required = true)
-    private File outputDirectory;
+    private String releaseVersion;
+
+    /**
+     * The template file, e.g., {@code src/changelog/changelog-email.txt.ftl}.
+     */
+    @Parameter(
+            property = ChangelogExporterArgs.TEMPLATE_FILE_PROPERTY_NAME,
+            required = true)
+    private File templateFile;
+
+    /**
+     * The output file, e.g., {@code /tmp/changelog-email.txt}.
+     */
+    @Parameter(
+            property = ChangelogExporterArgs.OUTPUT_FILE_PROPERTY_NAME,
+            required = true)
+    private File outputFile;
 
     public void execute() {
-        ChangelogExporterArgs args = new ChangelogExporterArgs(
+        ChangelogExporterArgs args = ChangelogExporterArgs.ofSingle(
                 changelogDirectory.toPath(),
-                outputDirectory.toPath());
+                releaseVersion,
+                templateFile.toPath(),
+                outputFile.toPath());
         ChangelogExporter.performExport(args);
     }
 
diff --git 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
similarity index 55%
copy from 
log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
copy to 
log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
index feb67fd..019c12c 100644
--- 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
+++ 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
@@ -17,47 +17,55 @@
 package org.apache.logging.log4j;
 
 import java.io.File;
-import java.time.LocalDate;
 
+import org.apache.logging.log4j.changelog.importer.MavenChangesImporter;
+import org.apache.logging.log4j.changelog.importer.MavenChangesImporterArgs;
 import org.apache.logging.log4j.changelog.releaser.ChangelogReleaser;
-import org.apache.logging.log4j.changelog.releaser.ChangelogReleaserArgs;
 
 import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
 /**
- * Goal moving the contents of an unreleased changelog directory (e.g., {@code 
.2.x.x} to a released one (e.g., {@code 2.19.0}).
+ * Goal populating a changelog directory from <a 
href="https://maven.apache.org/plugins/maven-changes-plugin/";>maven-changes-plugin</a>
 source XML.
  *
  * @see ChangelogReleaser
  */
-@Mojo(name = "release", defaultPhase = LifecyclePhase.VALIDATE)
-public class ReleaseMojo extends AbstractMojo {
+@Mojo(name = "import")
+public class ImportMojo extends AbstractMojo {
 
     /**
      * Directory containing release folders composed of changelog entry XML 
files.
      */
     @Parameter(
             defaultValue = "${project.basedir}/src/changelog",
-            property = ChangelogReleaserArgs.CHANGELOG_DIRECTORY_PROPERTY_NAME,
+            property = 
MavenChangesImporterArgs.CHANGELOG_DIRECTORY_PROPERTY_NAME,
             required = true)
     private File changelogDirectory;
 
     /**
-     * The version to be released, e.g., {@code 2.19.0}.
+     * <a 
href="https://maven.apache.org/plugins/maven-changes-plugin/";>maven-changes-plugin</a>
 source XML, {@code changes.xml}, location.
      */
     @Parameter(
-            property = ChangelogReleaserArgs.RELEASE_VERSION_PROPERTY_NAME,
+            defaultValue = "${project.basedir}/src/changes/changes.xml",
+            property = MavenChangesImporterArgs.CHANGES_XML_FILE_PROPERTY_NAME,
             required = true)
-    private String releaseVersion;
+    private File changesXmlFile;
+
+    /**
+     * The upcoming release version major number, e.g., {@code 2} for {@code 
2.x.x} releases.
+     */
+    @Parameter(
+            property = 
MavenChangesImporterArgs.RELEASE_VERSION_MAJOR_PROPERTY_NAME,
+            required = true)
+    private int releaseVersionMajor;
 
     public void execute() {
-        ChangelogReleaserArgs args = new ChangelogReleaserArgs(
+        MavenChangesImporterArgs args = new MavenChangesImporterArgs(
                 changelogDirectory.toPath(),
-                releaseVersion,
-                LocalDate.now());
-        ChangelogReleaser.performRelease(args);
+                changesXmlFile.toPath(),
+                releaseVersionMajor);
+        MavenChangesImporter.performImport(args);
     }
 
 }
diff --git 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
index feb67fd..a9cf870 100644
--- 
a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
+++ 
b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ReleaseMojo.java
@@ -23,7 +23,6 @@ import 
org.apache.logging.log4j.changelog.releaser.ChangelogReleaser;
 import org.apache.logging.log4j.changelog.releaser.ChangelogReleaserArgs;
 
 import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
@@ -32,7 +31,7 @@ import org.apache.maven.plugins.annotations.Parameter;
  *
  * @see ChangelogReleaser
  */
-@Mojo(name = "release", defaultPhase = LifecyclePhase.VALIDATE)
+@Mojo(name = "release")
 public class ReleaseMojo extends AbstractMojo {
 
     /**
diff --git a/log4j-changelog/README.adoc b/log4j-changelog/README.adoc
index 9e30d70..dc9e9a7 100644
--- a/log4j-changelog/README.adoc
+++ b/log4j-changelog/README.adoc
@@ -261,16 +261,14 @@ Simply create a <<#changelog-entry-file>> and commit it 
along with your change!
 [#qa-generate]
 === How can I export changelogs to AsciiDoc files?
 
-You need to use `ChangelogExporter` as follows:
+You need to use `ChangelogExporter`, which is accessible through the following 
goals of xref:../log4j-changelog-maven-plugin/README.adoc[the Maven plugin]:
 
-[source,bash]
-----
-java \
-    -cp /path/to/log4j-changelog.jar \
-    -Dlog4j.changelog.directory=/path/to/changelog/directory \
-    -Dlog4j.changelog.outputDirectory=/path/to/asciiDocOutputDirectory \
-    org.apache.logging.log4j.changelog.exporter.ChangelogExporter
-----
+`log4j-changelog:export`::
+It exports the entire changelog directory (e.g., `src/changelog`) using 
`.index.adoc.ftl` and `.changelog.adoc.ftl` template files.
+
+`log4j-changelog:export-single`::
+It exports a single release changelog directory against a particular template 
file.
+For instance, you can use it to export `src/changelog/2.19.0` using 
`src/changelog/.changelog-email.txt.ftl` to `/tmp/changelog-email.txt`.
 
 [#qa-deploy-release]
 === I am about to deploy a new Log4j release. What shall I do?
@@ -294,15 +292,13 @@ Hence, there are no differences between releases and 
release candidates.
 
 How to carry out aforementioned changes are explained below in steps:
 
-. Populate the `<changelogDirectory>/<releaseVersion>` directory (e.g., 
`/src/changelog/2.19.0`) from the upcoming release changelog directory (e.g., 
`<changelogDirectory>/.2.x.x`):
+. Populate the `<changelogDirectory>/<releaseVersion>` directory (e.g., 
`/src/changelog/2.19.0`) from the upcoming release changelog directory (e.g., 
`<changelogDirectory>/.2.x.x`) using the 
xref:../log4j-changelog-maven-plugin/README.adoc#release[`release` Maven goal]:
 +
 [source,bash]
 ----
-java \
-    -cp /path/to/log4j-changelog.jar \
+./mvnw log4j-changelog:release \
     -Dlog4j.changelog.directory=/path/to/changelog/directory \
-    -Dlog4j.changelog.releaseVersion=X.Y.Z \
-    org.apache.logging.log4j.changelog.releaser.ChangelogReleaser
+    -Dlog4j.changelog.releaseVersion=X.Y.Z
 ----
 . Verify that all changelog entry files are moved from 
`<changelogDirectory>/.<releaseVersionMajor>.x.x` directory (e.g., 
`/src/changelog/.2.x.x`)
 . Verify that `<changelogDirectory>/<releaseVersion>` directory (e.g., 
`/src/changelog/2.19.0`) is created, and it contains `.changelog.adoc.ftl`, 
`.release.xml`, and changelog entry files
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/ChangelogFiles.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/ChangelogFiles.java
index abca953..df35b37 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/ChangelogFiles.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/ChangelogFiles.java
@@ -56,10 +56,6 @@ public final class ChangelogFiles {
         return changelogDirectory.resolve(".index.adoc.ftl");
     }
 
-    public static String indexTemplateFile(final Path changelogDirectory, 
final Path baseDir) {
-        return 
baseDir.relativize(indexTemplateFile(changelogDirectory)).toString().replaceAll("\\\\",
 "/");
-    }
-
     public static Path releaseDirectory(final Path changelogDirectory, final 
String releaseVersion) {
         return changelogDirectory.resolve(releaseVersion);
     }
@@ -72,7 +68,4 @@ public final class ChangelogFiles {
         return releaseDirectory.resolve(".changelog.adoc.ftl");
     }
 
-    public static String releaseChangelogTemplateFile(final Path 
releaseDirectory, final Path baseDir) {
-        return 
baseDir.relativize(releaseChangelogTemplateFile(releaseDirectory)).toString().replaceAll("\\\\",
 "/");
-    }
 }
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java
index dc367a4..493ecb1 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java
@@ -34,15 +34,45 @@ public final class ChangelogExporter {
 
     private ChangelogExporter() {}
 
-    public static void main(final String[] mainArgs) {
-        final ChangelogExporterArgs args = 
ChangelogExporterArgs.fromSystemProperties();
-        performExport(args);
+    public static void performExport(final ChangelogExporterArgs args) {
+        Objects.requireNonNull(args, "args");
+        if (ChangelogExporterArgs.Mode.SINGLE.equals(args.mode)) {
+            performSingleExport(args.changelogDirectory, args.releaseVersion, 
args.templateFile, args.outputFile);
+        } else if (ChangelogExporterArgs.Mode.MULTI.equals(args.mode)) {
+            performMultiExport(args.changelogDirectory, args.outputDirectory);
+        } else {
+            throw new IllegalArgumentException("unknown mode: " + args.mode);
+        }
     }
 
-    public static void performExport(final ChangelogExporterArgs args) {
+    private static void performSingleExport(
+            final Path changelogDirectory,
+            final String releaseVersion,
+            final Path templateFile,
+            final Path outputFile) {
+        final Path releaseDirectory = 
ChangelogFiles.releaseDirectory(changelogDirectory, releaseVersion);
+        final Path releaseXmlFile = 
ChangelogFiles.releaseXmlFile(releaseDirectory);
+        final ChangelogRelease changelogRelease = 
ChangelogRelease.readFromXmlFile(releaseXmlFile);
+        final Map<ChangelogEntry.Type, List<ChangelogEntry>> 
changelogEntriesByType =
+                readChangelogEntriesByType(releaseDirectory);
+        try {
+            exportRelease(
+                    outputFile,
+                    changelogDirectory,
+                    changelogRelease,
+                    changelogEntriesByType,
+                    templateFile);
+        } catch (final IOException error) {
+            final String message =
+                    String.format("failed exporting release from directory 
`%s`", releaseDirectory);
+            throw new RuntimeException(message, error);
+        }
+    }
+
+    public static void performMultiExport(final Path changelogDirectory, final 
Path outputDirectory) {
 
         // Find release directories
-        final List<Path> releaseDirectories = findReleaseDirectories(args);
+        final List<Path> releaseDirectories = 
findReleaseDirectories(changelogDirectory);
         final int releaseDirectoryCount = releaseDirectories.size();
 
         // Read the release information files
@@ -69,8 +99,8 @@ public final class ChangelogExporter {
                 final int changelogEntryCount;
                 try {
                     changelogEntryCount = exportRelease(
-                            args.outputDirectory,
-                            args.changelogDirectory,
+                            outputDirectory,
+                            changelogDirectory,
                             releaseDirectory,
                             changelogRelease,
                             releaseChangelogTemplateFile);
@@ -96,19 +126,19 @@ public final class ChangelogExporter {
 
         // Export unreleased
         ChangelogFiles
-                .unreleasedDirectoryVersionMajors(args.changelogDirectory)
+                .unreleasedDirectoryVersionMajors(changelogDirectory)
                 .stream()
                 .sorted()
                 .forEach(upcomingReleaseVersionMajor -> {
                     final Path upcomingReleaseDirectory =
-                            
ChangelogFiles.unreleasedDirectory(args.changelogDirectory, 
upcomingReleaseVersionMajor);
+                            
ChangelogFiles.unreleasedDirectory(changelogDirectory, 
upcomingReleaseVersionMajor);
                     final ChangelogRelease upcomingRelease = 
upcomingRelease(upcomingReleaseVersionMajor);
                     final Path upcomingReleaseChangelogTemplateFile =
                             
ChangelogFiles.releaseChangelogTemplateFile(upcomingReleaseDirectory);
                     System.out.format("exporting upcoming release directory: 
`%s`%n", upcomingReleaseDirectory);
                     final int changelogEntryCount = exportRelease(
-                            args.outputDirectory,
-                            args.changelogDirectory,
+                            outputDirectory,
+                            changelogDirectory,
                             upcomingReleaseDirectory,
                             upcomingRelease,
                             upcomingReleaseChangelogTemplateFile);
@@ -118,16 +148,16 @@ public final class ChangelogExporter {
 
         // Export the release index
         exportIndex(
-                args.outputDirectory,
-                args.changelogDirectory,
+                outputDirectory,
+                changelogDirectory,
                 changelogReleases,
                 changelogEntryCounts);
 
     }
 
-    private static List<Path> findReleaseDirectories(ChangelogExporterArgs 
args) {
+    private static List<Path> findReleaseDirectories(final Path 
changelogDirectory) {
         return FileUtils.findAdjacentFiles(
-                args.changelogDirectory, true,
+                changelogDirectory, true,
                 paths -> paths
                         .filter(ChangelogExporter::isNonEmptyDirectory)
                         .sorted(Comparator.comparing(releaseDirectory -> {
@@ -152,9 +182,11 @@ public final class ChangelogExporter {
             final Path releaseChangelogTemplateFile) {
         final Map<ChangelogEntry.Type, List<ChangelogEntry>> 
changelogEntriesByType =
                 readChangelogEntriesByType(releaseDirectory);
+        final String releaseChangelogFileName = 
releaseChangelogFileName(changelogRelease);
+        final Path releaseChangelogFile = 
outputDirectory.resolve(releaseChangelogFileName);
         try {
             exportRelease(
-                    outputDirectory,
+                    releaseChangelogFile,
                     changelogDirectory,
                     changelogRelease,
                     changelogEntriesByType,
@@ -184,14 +216,12 @@ public final class ChangelogExporter {
     }
 
     private static void exportRelease(
-            final Path outputDirectory,
+            final Path outputFile,
             final Path changelogDirectory,
             final ChangelogRelease release,
             final Map<ChangelogEntry.Type, List<ChangelogEntry>> entriesByType,
             final Path releaseChangelogTemplateFile)
             throws IOException {
-        final String releaseChangelogFileName = 
releaseChangelogFileName(release);
-        final Path releaseChangelogFile = 
outputDirectory.resolve(releaseChangelogFileName);
         final Map<String, Object> releaseChangelogTemplateData = new 
LinkedHashMap<>();
         releaseChangelogTemplateData.put("release", release);
         releaseChangelogTemplateData.put("entriesByType", entriesByType);
@@ -200,7 +230,7 @@ public final class ChangelogExporter {
                 changelogDirectory,
                 releaseChangelogTemplateName,
                 releaseChangelogTemplateData,
-                releaseChangelogFile);
+                outputFile);
     }
 
     private static ChangelogRelease upcomingRelease(final int versionMajor) {
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterArgs.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterArgs.java
index 178eba6..ef827dc 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterArgs.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterArgs.java
@@ -19,7 +19,9 @@ package org.apache.logging.log4j.changelog.exporter;
 import java.nio.file.Path;
 import java.util.Objects;
 
-import static 
org.apache.logging.log4j.changelog.util.PropertyUtils.requireNonBlankPathProperty;
+import edu.umd.cs.findbugs.annotations.Nullable;
+
+import static 
org.apache.logging.log4j.changelog.util.VersionUtils.requireSemanticVersioning;
 
 public final class ChangelogExporterArgs {
 
@@ -27,19 +29,74 @@ public final class ChangelogExporterArgs {
 
     public static final String OUTPUT_DIRECTORY_PROPERTY_NAME = 
"log4j.changelog.outputDirectory";
 
+    public static final String RELEASE_VERSION_PROPERTY_NAME = 
"log4j.changelog.releaseVersion";
+
+    public static final String TEMPLATE_FILE_PROPERTY_NAME = 
"log4j.changelog.templateFile";
+
+    public static final String OUTPUT_FILE_PROPERTY_NAME = 
"log4j.changelog.outputFile";
+
+    public enum Mode { MULTI, SINGLE }
+
+    final Mode mode;
+
     final Path changelogDirectory;
 
+    @Nullable
     final Path outputDirectory;
 
-    public ChangelogExporterArgs(final Path changelogDirectory, final Path 
outputDirectory) {
-        this.changelogDirectory = Objects.requireNonNull(changelogDirectory, 
"changelogDirectory");
-        this.outputDirectory = Objects.requireNonNull(outputDirectory, 
"outputDirectory");
+    @Nullable
+    final String releaseVersion;
+
+    @Nullable
+    final Path templateFile;
+
+    @Nullable
+    final Path outputFile;
+
+    private ChangelogExporterArgs(
+            final Mode mode,
+            final Path changelogDirectory,
+            @Nullable final Path outputDirectory,
+            @Nullable final String releaseVersion,
+            @Nullable final Path templateFile,
+            @Nullable final Path outputFile) {
+        this.mode = mode;
+        this.changelogDirectory = changelogDirectory;
+        this.outputDirectory = outputDirectory;
+        this.releaseVersion = releaseVersion;
+        this.templateFile = templateFile;
+        this.outputFile = outputFile;
+    }
+
+    public static ChangelogExporterArgs of(final Path changelogDirectory, 
final Path outputDirectory) {
+        Objects.requireNonNull(changelogDirectory, "changelogDirectory");
+        Objects.requireNonNull(outputDirectory, "outputDirectory");
+        return new ChangelogExporterArgs(
+                Mode.MULTI,
+                changelogDirectory,
+                outputDirectory,
+                null,
+                null,
+                null);
     }
 
-    static ChangelogExporterArgs fromSystemProperties() {
-        final Path changelogDirectory = 
requireNonBlankPathProperty(CHANGELOG_DIRECTORY_PROPERTY_NAME);
-        final Path outputDirectory = 
requireNonBlankPathProperty(OUTPUT_DIRECTORY_PROPERTY_NAME);
-        return new ChangelogExporterArgs(changelogDirectory, outputDirectory);
+    public static ChangelogExporterArgs ofSingle(
+            final Path changelogDirectory,
+            final String releaseVersion,
+            final Path templateFile,
+            final Path outputFile) {
+        Objects.requireNonNull(changelogDirectory, "changelogDirectory");
+        Objects.requireNonNull(releaseVersion, "releaseVersion");
+        requireSemanticVersioning(releaseVersion, 
RELEASE_VERSION_PROPERTY_NAME);
+        Objects.requireNonNull(templateFile, "templateFile");
+        Objects.requireNonNull(outputFile, "outputFile");
+        return new ChangelogExporterArgs(
+                Mode.SINGLE,
+                changelogDirectory,
+                null,
+                releaseVersion,
+                templateFile,
+                outputFile);
     }
 
 }
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporter.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporter.java
index 33074f8..b28e735 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporter.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporter.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.changelog.importer;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 import org.apache.logging.log4j.changelog.ChangelogEntry;
 import org.apache.logging.log4j.changelog.ChangelogFiles;
@@ -30,12 +31,8 @@ public final class MavenChangesImporter {
 
     private MavenChangesImporter() {}
 
-    public static void main(final String[] mainArgs) {
-        final MavenChangesImporterArgs args = 
MavenChangesImporterArgs.fromSystemProperties();
-        performImport(args);
-    }
-
     public static void performImport(final MavenChangesImporterArgs args) {
+        Objects.requireNonNull(args, "args");
         final MavenChanges mavenChanges = 
MavenChanges.readFromFile(args.changesXmlFile);
         mavenChanges.releases.forEach(release -> {
             if ("TBD".equals(release.date)) {
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporterArgs.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporterArgs.java
index 515180b..a66ba4c 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporterArgs.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/importer/MavenChangesImporterArgs.java
@@ -19,9 +19,6 @@ package org.apache.logging.log4j.changelog.importer;
 import java.nio.file.Path;
 import java.util.Objects;
 
-import static 
org.apache.logging.log4j.changelog.util.PropertyUtils.requireNonBlankIntProperty;
-import static 
org.apache.logging.log4j.changelog.util.PropertyUtils.requireNonBlankPathProperty;
-
 public final class MavenChangesImporterArgs {
 
     public static final String CHANGELOG_DIRECTORY_PROPERTY_NAME = 
"log4j.changelog.directory";
@@ -58,11 +55,4 @@ public final class MavenChangesImporterArgs {
 
     }
 
-    static MavenChangesImporterArgs fromSystemProperties() {
-        final Path changelogDirectory = 
requireNonBlankPathProperty(CHANGELOG_DIRECTORY_PROPERTY_NAME);
-        final Path changesXmlFile = 
requireNonBlankPathProperty(CHANGES_XML_FILE_PROPERTY_NAME);
-        final int releaseVersionMajor = 
requireNonBlankIntProperty(RELEASE_VERSION_MAJOR_PROPERTY_NAME);
-        return new MavenChangesImporterArgs(changelogDirectory, 
changesXmlFile, releaseVersionMajor);
-    }
-
 }
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaser.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaser.java
index ed8ceb5..1096a06 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaser.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaser.java
@@ -21,6 +21,7 @@ import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.LocalDate;
+import java.util.Objects;
 
 import org.apache.logging.log4j.changelog.ChangelogFiles;
 import org.apache.logging.log4j.changelog.ChangelogRelease;
@@ -33,14 +34,10 @@ public final class ChangelogReleaser {
 
     private ChangelogReleaser() {}
 
-    public static void main(final String[] mainArgs) {
-        final ChangelogReleaserArgs args = 
ChangelogReleaserArgs.fromSystemProperties();
-        performRelease(args);
-    }
-
     public static void performRelease(final ChangelogReleaserArgs args) {
 
         // Read the release date and version
+        Objects.requireNonNull(args, "args");
         final String releaseDate = ISO_DATE.format(args.releaseDate != null ? 
args.releaseDate : LocalDate.now());
         final int releaseVersionMajor = 
VersionUtils.versionMajor(args.releaseVersion);
         System.out.format("using `%s` for the release date%n", releaseDate);
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaserArgs.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaserArgs.java
index 7951be4..cc02fa3 100644
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaserArgs.java
+++ 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaserArgs.java
@@ -20,8 +20,6 @@ import java.nio.file.Path;
 import java.time.LocalDate;
 import java.util.Objects;
 
-import static 
org.apache.logging.log4j.changelog.util.PropertyUtils.requireNonBlankPathProperty;
-import static 
org.apache.logging.log4j.changelog.util.PropertyUtils.requireNonBlankStringProperty;
 import static 
org.apache.logging.log4j.changelog.util.VersionUtils.requireSemanticVersioning;
 
 public final class ChangelogReleaserArgs {
@@ -54,11 +52,4 @@ public final class ChangelogReleaserArgs {
 
     }
 
-    static ChangelogReleaserArgs fromSystemProperties() {
-        final Path changelogDirectory = 
requireNonBlankPathProperty(CHANGELOG_DIRECTORY_PROPERTY_NAME);
-        final String releaseVersion = 
requireNonBlankStringProperty(RELEASE_VERSION_PROPERTY_NAME);
-        final LocalDate releaseDate = LocalDate.now();
-        return new ChangelogReleaserArgs(changelogDirectory, releaseVersion, 
releaseDate);
-    }
-
 }
diff --git 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/util/PropertyUtils.java
 
b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/util/PropertyUtils.java
deleted file mode 100644
index b50db01..0000000
--- 
a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/util/PropertyUtils.java
+++ /dev/null
@@ -1,61 +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.changelog.util;
-
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import edu.umd.cs.findbugs.annotations.Nullable;
-
-import static org.apache.logging.log4j.changelog.util.StringUtils.isBlank;
-
-public final class PropertyUtils {
-
-    private PropertyUtils() {}
-
-    public static Path requireNonBlankPathProperty(final String key) {
-        final String value = requireNonBlankStringProperty(key);
-        try {
-            return Paths.get(value);
-        } catch (final InvalidPathException error) {
-            final String message = String.format("system property `%s` doesn't 
contain a valid path: `%s`", key, value);
-            throw new IllegalArgumentException(message, error);
-        }
-    }
-
-    public static int requireNonBlankIntProperty(final String key) {
-        final String value = requireNonBlankStringProperty(key);
-        try {
-            return Integer.parseInt(value);
-        } catch (final NumberFormatException error) {
-            final String message = String.format("system property `%s` doesn't 
contain a valid integer: `%s`", key, value);
-            throw new IllegalArgumentException(message, error);
-        }
-    }
-
-    public static String requireNonBlankStringProperty(final String key) {
-        @Nullable
-        final String value = System.getProperty(key);
-        if (isBlank(value)) {
-            final String message = String.format("blank system property: 
`%s`", key);
-            throw new IllegalArgumentException(message);
-        }
-        return value;
-    }
-
-}
diff --git 
a/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogExporterTest.java
 
b/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogExporterTest.java
index f015bd1..7ae2ea4 100644
--- 
a/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogExporterTest.java
+++ 
b/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogExporterTest.java
@@ -31,12 +31,25 @@ import static 
org.apache.logging.log4j.changelog.FileTestUtils.assertDirectoryCo
 class ChangelogExporterTest {
 
     @Test
-    void output_should_match(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final 
Path outputDirectory) {
-        ChangelogExporterArgs args = new ChangelogExporterArgs(
+    void multi_mode_output_should_match(@TempDir(cleanup = 
CleanupMode.ON_SUCCESS) final Path outputDirectory) {
+        ChangelogExporterArgs args = ChangelogExporterArgs.of(
                 Paths.get("src/test/resources/3-enriched"),
                 outputDirectory);
         ChangelogExporter.performExport(args);
-        assertDirectoryContentMatches(outputDirectory, 
Paths.get("src/test/resources/4-exported"));
+        assertDirectoryContentMatches(outputDirectory, 
Paths.get("src/test/resources/4-exported-multi"));
+    }
+
+    @Test
+    void single_mode_with_releaseVersion_output_should_match(
+            @TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path 
outputDirectory) {
+        final Path outputFile = 
outputDirectory.resolve(".changelog-email.txt");
+        ChangelogExporterArgs args = ChangelogExporterArgs.ofSingle(
+                Paths.get("src/test/resources/3-enriched"),
+                "2.18.0",
+                
Paths.get("src/test/resources/3-enriched/.changelog-email.txt.ftl"),
+                outputFile);
+        ChangelogExporter.performExport(args);
+        assertDirectoryContentMatches(outputDirectory, 
Paths.get("src/test/resources/4-exported-single"));
     }
 
 }
diff --git 
a/log4j-changelog/src/test/resources/3-enriched/.changelog-email.txt.ftl 
b/log4j-changelog/src/test/resources/3-enriched/.changelog-email.txt.ftl
new file mode 100644
index 0000000..f99655f
--- /dev/null
+++ b/log4j-changelog/src/test/resources/3-enriched/.changelog-email.txt.ftl
@@ -0,0 +1,40 @@
+<#--
+  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
+
+      https://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.
+-->
+The Apache Log4j ${release.version} release is now available for voting.
+
+This release contains minor enhancements and bug fixes.
+
+Source repository: https://github.com/apache/logging-log4j2
+Tag: rel/${release.version}-rc.1
+
+Please download, test, and cast your votes on the Log4j developers list.
+
+[ ] +1, release the artifacts
+[ ] -1, don't release, because...
+
+The vote will remain open for 24 hours (or more if required). All votes are 
welcome and we encourage everyone to test the release, but only the Logging 
Services PMC votes are officially counted. At least 3 +1 votes and more 
positive than negative votes are required.
+
+<#if entriesByType?size gt 0>== Changes
+<#list entriesByType as entryType, entries>
+
+=== ${entryType?capitalize}
+
+<#list entries as entry>
+* ${entry.description.text?replace("\\s+", " ", "r")} (<#list entry.issues as 
issue>${issue.id}<#if issue?has_next>, </#if></#list>)
+</#list>
+</#list>
+</#if>
diff --git 
a/log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl 
b/log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl
index a8c18e8..188eff4 100644
--- a/log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl
@@ -17,7 +17,7 @@
 <#if entriesByType?size gt 0>== Changes
 <#list entriesByType as entryType, entries>
 
-== ${entryType?capitalize}
+=== ${entryType?capitalize}
 
 <#list entries as entry>
 * ${entry.description.text?replace("\\s+", " ", "r")} (for <@compress 
single_line=true>
diff --git a/log4j-changelog/src/test/resources/4-exported/2.17.2.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/2.17.2.adoc
similarity index 99%
rename from log4j-changelog/src/test/resources/4-exported/2.17.2.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/2.17.2.adoc
index 3a98b1c..a9dcac6 100644
--- a/log4j-changelog/src/test/resources/4-exported/2.17.2.adoc
+++ b/log4j-changelog/src/test/resources/4-exported-multi/2.17.2.adoc
@@ -46,11 +46,11 @@ For complete information on Apache Log4j 2, including 
instructions on how to sub
 
 == Changes
 
-== Removed
+=== Removed
 
 * Fix `ThreadContextDataInjector` initialization deadlock (for 
https://issues.apache.org/jira/browse/LOG4J2-3333[LOG4J2-3333] by Carter Kozak)
 * Document that the Spring Boot Lookup requires the `log4j-spring-boot` 
dependency (for https://issues.apache.org/jira/browse/LOG4J2-3405[LOG4J2-3405] 
by Ralph Goers)
 
-== Fixed
+=== Fixed
 
 * Flag `LogManager` as initialized if the `LoggerFactory` is provided as a 
property (for https://issues.apache.org/jira/browse/LOG4J2-3304[LOG4J2-3304] by 
Ralph Goers, francis-FY)
diff --git a/log4j-changelog/src/test/resources/4-exported/2.18.0.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/2.18.0.adoc
similarity index 98%
rename from log4j-changelog/src/test/resources/4-exported/2.18.0.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/2.18.0.adoc
index 2342565..b91a019 100644
--- a/log4j-changelog/src/test/resources/4-exported/2.18.0.adoc
+++ b/log4j-changelog/src/test/resources/4-exported-multi/2.18.0.adoc
@@ -35,14 +35,14 @@ For complete information on Apache Log4j 2, including 
instructions on how to sub
 
 == Changes
 
-== Added
+=== Added
 
 * Don't use `Paths.get()` to avoid circular file systems (for 
https://issues.apache.org/jira/browse/LOG4J2-3527[LOG4J2-3527] by Ralph Goers)
 
-== Removed
+=== Removed
 
 * The `DirectWriteRolloverStrategy` was not detecting the correct index to use 
during startup (for 
https://issues.apache.org/jira/browse/LOG4J2-3490[LOG4J2-3490] by Ralph Goers)
 
-== Fixed
+=== Fixed
 
 * `DirectWriteRolloverStrategy` should use the current time when creating 
files (for https://issues.apache.org/jira/browse/LOG4J2-3339[LOG4J2-3339] by 
Ralph Goers)
diff --git a/log4j-changelog/src/test/resources/4-exported/2.x.x.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/2.x.x.adoc
similarity index 98%
rename from log4j-changelog/src/test/resources/4-exported/2.x.x.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/2.x.x.adoc
index 04eed2a..0f3cfd9 100644
--- a/log4j-changelog/src/test/resources/4-exported/2.x.x.adoc
+++ b/log4j-changelog/src/test/resources/4-exported-multi/2.x.x.adoc
@@ -21,11 +21,11 @@ Changes staged for the next 2.x.x version that is yet to be 
released.
 
 == Changes
 
-== Changed
+=== Changed
 
 * Add `getExplicitLevel` method to `LoggerConfig` (for 
https://issues.apache.org/jira/browse/LOG4J2-3572[LOG4J2-3572] by Ralph Goers)
 
-== Fixed
+=== Fixed
 
 * Make `StatusConsoleListener` use `SimpleLogger` internally (for 
https://issues.apache.org/jira/browse/LOG4J2-3584[LOG4J2-3584] by Volkan Yazıcı)
 * Harden `InstantFormatter` against delegate failures (for 
https://issues.apache.org/jira/browse/LOG4J2-3614[LOG4J2-3614] by Volkan 
Yazıcı, strainu)
diff --git a/log4j-changelog/src/test/resources/4-exported/3.x.x.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/3.x.x.adoc
similarity index 98%
rename from log4j-changelog/src/test/resources/4-exported/3.x.x.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/3.x.x.adoc
index 863658c..cb75630 100644
--- a/log4j-changelog/src/test/resources/4-exported/3.x.x.adoc
+++ b/log4j-changelog/src/test/resources/4-exported-multi/3.x.x.adoc
@@ -21,11 +21,11 @@ Changes staged for the next 3.x.x version that is yet to be 
released.
 
 == Changes
 
-== Added
+=== Added
 
 * Add `@Ordered` annotation to support plugin ordering when two or more 
plugins within the same category have the same case-insensitive name (for 
https://issues.apache.org/jira/browse/LOG4J2-857[LOG4J2-857] by Matt Sicker)
 
-== Changed
+=== Changed
 
 * Simplify Maven `site` phase and align it with the one in `release-2.x` 
branch (for https://github.com/apache/logging-log4j2/pull/1220[1220] by Volkan 
Yazıcı)
 * Switch the issue tracker from 
https://issues.apache.org/jira/browse/LOG4J2[JIRA] to 
https://github.com/apache/logging-log4j2/issues[GitHub Issues] (for 
https://github.com/apache/logging-log4j2/pull/1221[1221] by Volkan Yazıcı)
diff --git a/log4j-changelog/src/test/resources/4-exported/4.x.x.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/4.x.x.adoc
similarity index 100%
rename from log4j-changelog/src/test/resources/4-exported/4.x.x.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/4.x.x.adoc
diff --git a/log4j-changelog/src/test/resources/4-exported/index.adoc 
b/log4j-changelog/src/test/resources/4-exported-multi/index.adoc
similarity index 100%
rename from log4j-changelog/src/test/resources/4-exported/index.adoc
rename to log4j-changelog/src/test/resources/4-exported-multi/index.adoc
diff --git 
a/log4j-changelog/src/test/resources/4-exported-single/.changelog-email.txt 
b/log4j-changelog/src/test/resources/4-exported-single/.changelog-email.txt
new file mode 100644
index 0000000..96a2cf5
--- /dev/null
+++ b/log4j-changelog/src/test/resources/4-exported-single/.changelog-email.txt
@@ -0,0 +1,27 @@
+The Apache Log4j 2.18.0 release is now available for voting.
+
+This release contains minor enhancements and bug fixes.
+
+Source repository: https://github.com/apache/logging-log4j2
+Tag: rel/2.18.0-rc.1
+
+Please download, test, and cast your votes on the Log4j developers list.
+
+[ ] +1, release the artifacts
+[ ] -1, don't release, because...
+
+The vote will remain open for 24 hours (or more if required). All votes are 
welcome and we encourage everyone to test the release, but only the Logging 
Services PMC votes are officially counted. At least 3 +1 votes and more 
positive than negative votes are required.
+
+== Changes
+
+=== Added
+
+* Don't use `Paths.get()` to avoid circular file systems (LOG4J2-3527)
+
+=== Removed
+
+* The `DirectWriteRolloverStrategy` was not detecting the correct index to use 
during startup (LOG4J2-3490)
+
+=== Fixed
+
+* `DirectWriteRolloverStrategy` should use the current time when creating 
files (LOG4J2-3339)
diff --git 
a/log4j-changelog/src/test/resources/5-released/.changelog-email.txt.ftl 
b/log4j-changelog/src/test/resources/5-released/.changelog-email.txt.ftl
new file mode 100644
index 0000000..f99655f
--- /dev/null
+++ b/log4j-changelog/src/test/resources/5-released/.changelog-email.txt.ftl
@@ -0,0 +1,40 @@
+<#--
+  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
+
+      https://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.
+-->
+The Apache Log4j ${release.version} release is now available for voting.
+
+This release contains minor enhancements and bug fixes.
+
+Source repository: https://github.com/apache/logging-log4j2
+Tag: rel/${release.version}-rc.1
+
+Please download, test, and cast your votes on the Log4j developers list.
+
+[ ] +1, release the artifacts
+[ ] -1, don't release, because...
+
+The vote will remain open for 24 hours (or more if required). All votes are 
welcome and we encourage everyone to test the release, but only the Logging 
Services PMC votes are officially counted. At least 3 +1 votes and more 
positive than negative votes are required.
+
+<#if entriesByType?size gt 0>== Changes
+<#list entriesByType as entryType, entries>
+
+=== ${entryType?capitalize}
+
+<#list entries as entry>
+* ${entry.description.text?replace("\\s+", " ", "r")} (<#list entry.issues as 
issue>${issue.id}<#if issue?has_next>, </#if></#list>)
+</#list>
+</#list>
+</#if>
diff --git 
a/log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl 
b/log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl
index a8c18e8..188eff4 100644
--- a/log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl
@@ -17,7 +17,7 @@
 <#if entriesByType?size gt 0>== Changes
 <#list entriesByType as entryType, entries>
 
-== ${entryType?capitalize}
+=== ${entryType?capitalize}
 
 <#list entries as entry>
 * ${entry.description.text?replace("\\s+", " ", "r")} (for <@compress 
single_line=true>
diff --git a/log4j-tools-parent/pom.xml b/log4j-tools-parent/pom.xml
index ab77989..1467626 100644
--- a/log4j-tools-parent/pom.xml
+++ b/log4j-tools-parent/pom.xml
@@ -207,6 +207,8 @@
             <!-- License headers in GitHub templates pollute the prompt 
displayed to the user: -->
             <exclude>.github/ISSUE_TEMPLATE/*.md</exclude>
             <exclude>.github/pull_request_template.md</exclude>
+            <!-- We cannot place a license header into an email: -->
+            
<exclude>src/test/resources/4-exported-single/.changelog-email.txt</exclude>
           </excludes>
         </configuration>
         <executions>

Reply via email to