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 2023/03/13 11:58:17 UTC

[logging-log4j-tools] 05/05: Support multiple and formatting-agnostic template files (#44)

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

commit 1fd56f0f36cedbcab60acda3bc5db5298ca39a5e
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Mon Mar 13 12:38:23 2023 +0100

    Support multiple and formatting-agnostic template files (#44)
---
 CHANGELOG.adoc                                     |   2 +
 log4j-changelog-maven-plugin/README.adoc           |  85 +++++++--
 .../java/org/apache/logging/log4j/ExportMojo.java  |  46 ++++-
 .../java/org/apache/logging/log4j/ImportMojo.java  |   4 +-
 .../java/org/apache/logging/log4j/ReleaseMojo.java |   4 +-
 .../org/apache/logging/log4j/TemplateMojo.java     |  49 +++++
 log4j-changelog/README.adoc                        | 210 +++++++++------------
 .../logging/log4j/changelog/ChangelogFiles.java    |   8 +-
 .../changelog/exporter/ChangelogExporter.java      |  80 ++++----
 .../changelog/exporter/ChangelogExporterArgs.java  |  13 +-
 .../exporter/ChangelogExporterTemplate.java        |  83 ++++++++
 .../log4j/changelog/exporter/FreeMarkerUtils.java  |   3 +-
 .../changelog/releaser/ChangelogReleaser.java      |  30 ++-
 .../changelog/releaser/ChangelogReleaserArgs.java  |  15 +-
 .../log4j/changelog/releaser/VersionUtils.java     |   8 +-
 .../src/main/resources/log4j-changelog.xsd         |   4 +-
 .../log4j/changelog/ChangelogExporterTest.java     |  17 +-
 .../log4j/changelog/ChangelogReleaserTest.java     |   2 +-
 ....changelog.adoc.ftl => .release-notes.adoc.ftl} |   5 +-
 .../3-enriched/.2.x.x/.release-notes.common.ftl    |  18 ++
 .../3-enriched/.2.x.x/.release-notes.txt.ftl       |  21 +++
 .../.3.x.x/.release-notes.adoc.ftl}                |   2 +-
 .../.4.x.x/.release-notes.adoc.ftl}                |   2 +-
 ...ngelog-entries.adoc.ftl => .changelog.adoc.ftl} |   2 +-
 .../test/resources/3-enriched/.changelog.txt.ftl   |  26 +++
 .../src/test/resources/3-enriched/.index.adoc.ftl  |   4 +-
 .../2.17.2/.release-notes.adoc.ftl}                |   2 +-
 .../2.18.0/.release-notes.adoc.ftl}                |   2 +-
 .../src/test/resources/4-exported/2.17.2.adoc      |   4 +-
 .../src/test/resources/4-exported/2.18.0.adoc      |   6 +-
 .../src/test/resources/4-exported/2.x.x.adoc       |   4 +-
 .../src/test/resources/4-exported/2.x.x.txt        |  15 ++
 .../src/test/resources/4-exported/3.x.x.adoc       |   4 +-
 .../src/test/resources/4-exported/index.adoc       |   2 +-
 .../.3.x.x/.release-notes.adoc.ftl}                |   2 +-
 .../.4.x.x/.release-notes.adoc.ftl}                |   2 +-
 ...ngelog-entries.adoc.ftl => .changelog.adoc.ftl} |   2 +-
 .../test/resources/5-released/.changelog.txt.ftl   |  26 +++
 .../src/test/resources/5-released/.index.adoc.ftl  |   4 +-
 .../2.17.2/.release-notes.adoc.ftl}                |   2 +-
 .../2.18.0/.release-notes.adoc.ftl}                |   2 +-
 .../5-released/2.19.0/.changelog.adoc.ftl          |  22 ---
 .../.release-notes.adoc.ftl}                       |   5 +-
 .../5-released/2.19.0/.release-notes.common.ftl    |  18 ++
 .../5-released/2.19.0/.release-notes.txt.ftl       |  21 +++
 log4j-tools-parent/pom.xml                         |   1 +
 46 files changed, 626 insertions(+), 263 deletions(-)

diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index ca675b3..39b760a 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -17,6 +17,8 @@ limitations under the License.
 
 == 0.x.x
 
+* Support multiple and formatting-agnostic template files (for https://github.com/apache/logging-log4j-tools/issues/44[#44] 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 1a362d5..63d8e2f 100644
--- a/log4j-changelog-maven-plugin/README.adoc
+++ b/log4j-changelog-maven-plugin/README.adoc
@@ -15,32 +15,95 @@ See the License for the specific language governing permissions and
 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].
+:freemarker-link: https://freemarker.apache.org[FreeMarker]
+
+This project ships a Maven plugin providing access to the xref:../log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java[`ChangelogExporter`] and xref:../log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/ChangelogReleaser.java[`ChangelogReleaser`] of xref:../log4j-changelog/README.adoc[`log4j-changelog`].
 
 [#export]
 == Exporting changelogs
 
-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.
+_Exporting_ changelogs is the act of feeding provided changelog and release information into {freemarker-link} templates to generate certain files; e.g., release notes for the website.
+There are two types template files supported:
+
+xref:#changelog-template[Changelog templates]::
+These templates are rendered with the release and changelog information of a particular release.
+These are generally used to generate release notes for a particular release.
+
+xref:#index-template[Index templates]::
+These templates are rendered with the release information of all releases.
+These are generally used to generate the index page referencing to release notes of each release.
+
+See xref:../log4j-changelog/README.adoc#export[the `log4j-changelog` documentation] for further details.
 
-.Export AsciiDoc-formatted release notes to `target/generated-sources/site/asciidoc/changelog`
+You can use the `export` goal as follows:
+
+.`build > plugins` block entry of `pom.xml`
 [source,bash]
 ----
-./mvnw -N log4j-changelog:export
+<!-- Export AsciiDoc-formatted release notes -->
+<plugin>
+  <groupId>org.apache.logging.log4j</groupId>
+  <artifactId>log4j-changelog-maven-plugin</artifactId>
+  <inherited>false</inherited>
+  <configuration>
+    <indexTemplates>
+      <template>
+        <source>.index.adoc.ftl</source>
+      </template>
+    </indexTemplates>
+    <changelogTemplates>
+      <template>
+        <source>.release-notes.adoc.ftl</source>
+        <target>%v.adoc</target>
+      </template>
+    </changelogTemplates>
+  </configuration>
+  <executions>
+    <execution>
+      <id>generate-changelog</id>
+      <goals>
+        <goal>export</goal>
+      </goals>
+    </execution>
+  </executions>
+</plugin>
 ----
 
-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:
+`export` goal by default runs during the `pre-site` phase and accepts the following configuration:
 
-`changelogDirectory`::
+`changelogDirectory` (parameter)::
 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.
 
-`outputDirectory`::
-Directory to write generated changelog files.
+`outputDirectory` (parameter)::
+Directory to write rendered templates.
 It defaults to `${project.build.directory}/generated-sources/site/asciidoc/changelog` and can be set using the `log4j.changelog.exporter.outputDirectory` property.
 
+`indexTemplates` (parameter)::
+List of xref:#export-template-type[template]s that will be rendered with release information of all releases.
+See xref:../log4j-changelog/README.adoc#index-template-file[the index template file documentation] for details.
+
+`changelogTemplates` (parameter)::
+List of xref:#export-template-type[template]s that will be rendered with release and changelog information of a particular release.
+See xref:../log4j-changelog/README.adoc#changelog-template-file[the changelog template file documentation] for details.
+
+[#export-template-type]
+`Template` (type)::
+An object composed of following fields:
++
+`source` (parameter):::
+the {freemarker-link} template file
+
+`target` (parameter):::
+The output file.
+If not provided, it will be derived from the `source`: if the `source` is `.index.adoc.ftl`, the `target` will be set to `index.adoc`.
+If the value contains a `%v` (e.g., `%v.adoc`), it will be replaced with the associated release version.
+`%v` substitution is only allowed for changelog templates and will not work for index templates.
+
+`failIfNotFound` (parameter):::
+Indicates if export should fail when the source cannot be found.
+Defaults to `false`.
+
 [#release]
 == Populating a release changelog directory
 
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 a25f3f3..d598c8b 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
@@ -17,9 +17,15 @@
 package org.apache.logging.log4j;
 
 import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
+import org.apache.logging.log4j.changelog.ChangelogFiles;
 import org.apache.logging.log4j.changelog.exporter.ChangelogExporter;
 import org.apache.logging.log4j.changelog.exporter.ChangelogExporterArgs;
+import org.apache.logging.log4j.changelog.exporter.ChangelogExporterTemplate;
 
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugins.annotations.LifecyclePhase;
@@ -32,7 +38,10 @@ import org.apache.maven.plugins.annotations.Parameter;
  * @see ChangelogExporter
  */
 @Mojo(name = "export", defaultPhase = LifecyclePhase.PRE_SITE)
-public class ExportMojo extends AbstractMojo {
+public final class ExportMojo extends AbstractMojo {
+
+    private static final String SOURCE_TARGET_TEMPLATE_PATTERN =
+            "^\\.(.*)\\." + ChangelogFiles.templateFileNameExtension() + '$';
 
     /**
      * Directory containing release folders composed of changelog entry XML files.
@@ -44,7 +53,19 @@ public class ExportMojo extends AbstractMojo {
     private File changelogDirectory;
 
     /**
-     * Directory to write generated changelog files.
+     * Templates that will be rendered with the release information of all releases, e.g., to generate an index page.
+     */
+    @Parameter(required = true)
+    private List<TemplateMojo> indexTemplates;
+
+    /**
+     * Templates that will be rendered with the release and changelog information of a particular release, e.g., to generate the release notes.
+     */
+    @Parameter(required = true)
+    private List<TemplateMojo> changelogTemplates;
+
+    /**
+     * Directory to write rendered templates.
      */
     @Parameter(
             defaultValue = "${project.build.directory}/generated-sources/site/asciidoc/changelog",
@@ -53,10 +74,29 @@ public class ExportMojo extends AbstractMojo {
     private File outputDirectory;
 
     public void execute() {
-        ChangelogExporterArgs args = new ChangelogExporterArgs(
+        final Set<ChangelogExporterTemplate> translatedIndexTemplates = toExporterTemplates(indexTemplates);
+        final Set<ChangelogExporterTemplate> translatedReleaseChangelogTemplates = toExporterTemplates(
+                changelogTemplates);
+        final ChangelogExporterArgs args = new ChangelogExporterArgs(
                 changelogDirectory.toPath(),
+                translatedIndexTemplates,
+                translatedReleaseChangelogTemplates,
                 outputDirectory.toPath());
         ChangelogExporter.performExport(args);
     }
 
+    private static Set<ChangelogExporterTemplate> toExporterTemplates(Collection<TemplateMojo> templateMojos) {
+        return templateMojos
+                .stream()
+                .map(ExportMojo::toExporterTemplate)
+                .collect(Collectors.toSet());
+    }
+
+    private static ChangelogExporterTemplate toExporterTemplate(TemplateMojo templateMojo) {
+        final String target = templateMojo.target == null
+                ? templateMojo.source.replaceAll(SOURCE_TARGET_TEMPLATE_PATTERN, "$1")
+                : templateMojo.target;
+        return new ChangelogExporterTemplate(templateMojo.source, target, templateMojo.failIfNotFound);
+    }
+
 }
diff --git a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
index fac8e92..2c958b4 100644
--- a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
+++ b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/ImportMojo.java
@@ -32,7 +32,7 @@ import org.apache.maven.plugins.annotations.Parameter;
  * @see ChangelogReleaser
  */
 @Mojo(name = "import")
-public class ImportMojo extends AbstractMojo {
+public final class ImportMojo extends AbstractMojo {
 
     /**
      * Directory containing release folders composed of changelog entry XML files.
@@ -61,7 +61,7 @@ public class ImportMojo extends AbstractMojo {
     private int releaseVersionMajor;
 
     public void execute() {
-        MavenChangesImporterArgs args = new MavenChangesImporterArgs(
+        final MavenChangesImporterArgs args = new MavenChangesImporterArgs(
                 changelogDirectory.toPath(),
                 changesXmlFile.toPath(),
                 releaseVersionMajor);
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 9d57a73..5a94cb9 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
@@ -33,7 +33,7 @@ import org.apache.maven.plugins.annotations.Parameter;
  * @see ChangelogReleaser
  */
 @Mojo(name = "release", defaultPhase = LifecyclePhase.VALIDATE)
-public class ReleaseMojo extends AbstractMojo {
+public final class ReleaseMojo extends AbstractMojo {
 
     /**
      * Directory containing release folders composed of changelog entry XML files.
@@ -53,7 +53,7 @@ public class ReleaseMojo extends AbstractMojo {
     private String releaseVersion;
 
     public void execute() {
-        ChangelogReleaserArgs args = new ChangelogReleaserArgs(
+        final ChangelogReleaserArgs args = new ChangelogReleaserArgs(
                 changelogDirectory.toPath(),
                 releaseVersion,
                 LocalDate.now());
diff --git a/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/TemplateMojo.java b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/TemplateMojo.java
new file mode 100644
index 0000000..caccd7e
--- /dev/null
+++ b/log4j-changelog-maven-plugin/src/main/java/org/apache/logging/log4j/TemplateMojo.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.apache.maven.plugins.annotations.Parameter;
+
+/**
+ * The template file definition.
+ */
+public final class TemplateMojo {
+
+    /**
+     * The source template file name, e.g., {@code .index.adoc.ftl}, {@code .release-notes.adoc.ftl}.
+     */
+    @Parameter(required = true)
+    String source;
+
+    /**
+     * The target file name to export, e.g., {@code index.adoc}, {@code release-notes.adoc}, or, if the file is in a release changelog directory, {@code %v.adoc}, where {@code %v} will be replaced with the release version.
+     * <p>
+     * If not provided, the {@code source} will be used after dropping the dot prefix and {@code .ftl} suffix.
+     * That is, for source {@code .index.adoc.ftl}, {@code index.adoc} target will be used.
+     * </p>
+     *
+     */
+    @Parameter
+    String target;
+
+    /**
+     * Indicates if export should fail when the source cannot be found.
+     */
+    @Parameter(defaultValue = "false")
+    boolean failIfNotFound;
+
+}
diff --git a/log4j-changelog/README.adoc b/log4j-changelog/README.adoc
index 8765fee..5f99243 100644
--- a/log4j-changelog/README.adoc
+++ b/log4j-changelog/README.adoc
@@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
 limitations under the License.
 ////
 
+:freemarker-link: https://freemarker.apache.org[FreeMarker]
+
 This project contains tooling for changelogs of Apache Log4j projects.
 
 [#what-is-a-changelog]
@@ -36,120 +38,58 @@ Similar to `maven-changes-plugin`, changelog sources and their exports (e.g., As
 [#look]
 == What does it look like?
 
-All changelog _sources_ are stored in folders under _changelog directory_ (e.g., `/src/changelog`):
-
-[source]
-----
-$ ls -a -1 src/changelog
-.
-..
-# ...
-2.18.0
-2.19.0
-.2.x.x
-.index.adoc.ftl
-----
-
-Changelog sources of _released versions_ are stored in `<changelogDirectory>/<releaseVersion>` folders (e.g., `/src/changelog/2.19.0`):
+All changelog _sources_ and templates used to _export_ them are stored in folders under _the changelog directory_ (e.g., `/src/changelog`):
 
 [source]
 ----
-$ ls -a -1 src/changelog/2.19.0
-.
-..
-.changelog.adoc.ftl
-LOG4J2-2975_Add_implementation_of_SLF4J2_fluent_API.xml
-LOG4J2-3545_Add_correct_manifest_entries_for_OSGi_to_log4j_jcl.xml
-LOG4J2-3548_Improve_support_for_passwordless_keystores.xml
-# ...
-LOG4J2-3589_Allow_Plugins_to_be_injected_with_the_LoggerContext_referenc.xml
-LOG4J2-3590_Remove_SLF4J_1_8_x_binding.xml
-LOG4J2-3614_Harden_InstantFormatter_against_delegate_failures.xml
-LOG4J2-708_Add_async_support_to_Log4jServletFilter.xml
-.release.xml
+$ tree -a src/changelog
+├── 2.18.0                              # <1>
+│   ├── LOG4J2-3424_Properties_defined_in_configuration_using_a_value_attribute.xml   # <4>
+│   ├── LOG4J2-3425_Syslog_appender_lacks_the_SocketOptions_setting.xml               # <4>
+│   ├── LOG4J2-3426_Log4j_1_2_bridge_should_not_wrap_components_unnecessarily.xml     # <4>
+│   ├── LOG4J2-3427_Improves_ServiceLoader_support_on_servlet_containers.xml          # <4>
+│   ├── .release-notes.adoc.ftl         # <5>
+│   └── .release.xml                    # <3>
+├── 2.19.0                              # <1>
+│   ├── LOG4J2-3588_Allow_PropertySources_to_be_added.xml                             # <4>
+│   ├── LOG4J2-3590_Remove_SLF4J_1_8_x_binding.xml                                    # <4>
+│   ├── LOG4J2-3614_Harden_InstantFormatter_against_delegate_failures.xml             # <4>
+│   ├── LOG4J2-3556_JsonTemplateLayout_stack_trace_truncation_fix.xml                 # <4>
+│   ├── .release-notes.adoc.ftl         # <5>
+│   └── .release.xml                    # <3>
+├── .2.x.x                              # <2>
+│   ├── LOG4J2-1284_redirect_old_javadoc_urls.xml                                     # <4>
+│   └── .release-notes.adoc.ftl         # <5>
+├── .changelog.adoc.ftl                 # <5>
+└── .index.adoc.ftl                     # <5>
 ----
+<1> Changelog sources of _released versions_ are stored in `<changelogDirectory>/<releaseVersion>` folders (e.g., `src/changelog/2.19.0`)
+<2> Changelog sources of _upcoming releases_ are stored in `<changelogDirectory>/.<releaseVersionMajor>.x.x` folders (e.g., `/src/changelog/.2.x.x`)
+<3> `.release.xml` contains the information about the associated release.
+Note that upcoming release folders (e.g., `src/changelog/.2.x.x`) don't contain a `.release.xml`, since these releases are by definition not done yet.
+<4> All XML files not prefixed with a `.` (e.g., `src/changelog/.2.x.x/LOG4J2-3628_new_changelog_infra.xml`) constitute changelog entries
+<5> {freemarker-link} templates are used to _export_ this information to various forms; AsciiDoc-formatted pages for the website, Markdown-formatted files for GitHub Releases, etc.
 
-Changelog sources of _upcoming releases_ are stored in `<changelogDirectory>/.<releaseVersionMajor>.x.x` folders (e.g., `/src/changelog/.2.x.x`):
+[#release-entry-file]
+== Release entry file
 
-[source]
-----
-$ ls -a -1 src/changelog/.2.x.x
-.
-..
-.changelog.adoc.ftl
-LOG4J2-2678_Add_LogEvent_timestamp_to_ProducerRecord_in_KafkaAppender.xml
-LOG4J2-3628_new_changelog_infra.xml
-LOG4J2-3631_Fix_Configurator_setLevel_for_internal_classes.xml
-LOG4J2-3634_Fix_level_propagation_in_Log4jBridgeHandler.xml
-----
+A release entry file, `.release.xml`, consists of meta information regarding a particular release.
+A sample _release entry_ file is shared below.
 
-A typical `.release.xml` looks as follows:
-
-[source]
+.`src/changelog/2.19.0/release.xml` file contents
+[source,xml]
 ----
-$ cat src/changelog/2.19.0/release.xml
 <?xml version="1.0" encoding="UTF-8"?>
 <release xmlns="http://logging.apache.org/log4j/changelog"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://logging.apache.org/log4j/changelog https://logging.apache.org/log4j/changelog-0.1.0.xsd"
-         date="2022-09-09" version="2.19.0"/>
-----
-
-A typical changelog entry file looks as follows:
-
-[source]
-----
-$ cat src/changelog/.2.x.x/LOG4J2-3628_new_changelog_infra.xml
-<?xml version="1.0" encoding="UTF-8"?>
-<entry xmlns="http://logging.apache.org/log4j/changelog"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://logging.apache.org/log4j/changelog https://logging.apache.org/log4j/changelog-0.1.0.xsd"
-       type="fixed">
-  <issue id="LOG4J2-3556" link="https://issues.apache.org/jira/browse/LOG4J2-3556"/>
-  <author id="vy"/>
-  <author name="Arthur Gavlyukovskiy"/>
-  <description format="asciidoc">
-    Make `JsonTemplateLayout` stack trace truncation operate for each label block
-  </description>
-</entry>
-----
-
-All changelog folders, including the ones for the upcoming releases, can be exported to AsciiDoc:
-
-[source]
+         xsi:schemaLocation="
+             http://logging.apache.org/log4j/changelog
+             https://logging.apache.org/log4j/changelog-0.1.1.xsd"
+         date="2022-09-09"
+         version="2.19.0"/>
 ----
-$ ls -1 target/generated-sources/site/asciidoc/changelog
-# ...
-2.18.0.adoc
-2.19.0.adoc
-2.x.x.adoc
-index.adoc
-----
-
-[#released-version-changelogs]
-== Released version changelogs
-
-Changelogs of past released versions are contained in `<changelogDirectory>/<releaseVersion>` directories (e.g., `/src/changelog/2.19.0`).
-A released version changelog directory consists of following files:
 
-`.release.xml`::
-the meta information about the release (e.g., release version and date)
-
-`.changelog.adoc.ftl`::
-FreeMarker templated AsciiDoc file used by `ChangelogExporter`
-
-`[<issueId>_]<shortSummary>.xml`::
-changelog entry associated with a change
-
-`ChangelogExporter` compiles these source files and outputs a `<releaseVersion>.adoc` (e.g., `2.19.0.adoc`) file for each release.
-
-[#unreleased-version-changelogs]
-== Unreleased version changelogs
-
-Changelogs of upcoming release versions are stored in `<changelogDirectory>/.<releaseVersionMajor>.x.x` directories (e.g., `/src/changelog/.2.x.x`).
-Compared to released version changelog directories (e.g., `2.19.0`), `.<releaseVersionMajor>.x.x` directories do not contain a `.release.xml`.
-
-`ChangelogExporter` compiles these source files and outputs `<releaseVersionMajor>.x.x.adoc` (e.g., `2.x.x.adoc`) file for each upcoming release.
+Note that upcoming release folders (e.g., `src/changelog/.2.x.x`) don't contain a `.release.xml`, since these releases are by definition not done yet.
 
 [#changelog-entry-file]
 == Changelog entry file
@@ -164,12 +104,14 @@ Consider the following examples:
 
 A sample _changelog entry_ file is shared below.
 
-.`src/changelog/LOG4J2-3556_JsonTemplateLayout_stack_trace_truncation_fix.xml` file contents
+.`src/changelog/2.19.0/LOG4J2-3556_JsonTemplateLayout_stack_trace_truncation_fix.xml` file contents
 [source,xml]
 ----
 <entry xmlns="http://logging.apache.org/log4j/changelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://logging.apache.org/log4j/changelog https://logging.apache.org/log4j/changelog-0.1.0.xsd"
+       xsi:schemaLocation="
+           http://logging.apache.org/log4j/changelog
+           https://logging.apache.org/log4j/changelog-0.1.1.xsd"
        type="fixed">
   <issue id="LOG4J2-3556" link="https://issues.apache.org/jira/browse/LOG4J2-3556"/>
   <author id="vy"/>
@@ -192,26 +134,43 @@ Some remarks about the structure of changelog entry files:
 * `issue` element is optional, and, if present, must contain `id` and `link` attributes
 * `author` element must have at least one of `id` or `name` attributes
 * There must be at least one `author`
-* There must be a single `description` element with non-blank content and `format="asciidoc"` attribute
+* There must be a single `description` element with non-blank content and `format` attribute
+
+[#export]
+== Exporting changelogs
+
+_Exporting_ changelogs is the act of feeding provided changelog and release information into {freemarker-link} templates to generate certain files; e.g., release notes for the website.
+There are two types template files supported:
+
+xref:#changelog-template[Changelog templates]::
+These templates are rendered with the release and changelog information of a particular release.
+These are generally used to generate release notes for a particular release.
+
+xref:#index-template[Index templates]::
+These templates are rendered with the release information of all releases.
+These are generally used to generate the index page referencing to release notes of each release.
+
+xref:src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporter.java[`ChangelogExporter`] is responsible for performing the export operation.
 
-[#changelog-template-file]
-== Changelog template file
+[#changelog-template]
+=== Changelog templates
 
-Each `.changelog.adoc.ftl` FreeMarker templated AsciiDoc files are compiled by `ChangelogExporter` with the following input data hash:
+Changelog template files (e.g., `src/changelog/2.19.0/.release-notes.adoc.ftl` ) are rendered with the release and changelog information of a particular release using the following input data hash:
 
-* `release` -> `ChangelogRelease`
-* `entriesByType` -> `Map<ChangelogEntry.Type, List<ChangelogEntry>>`
+* `release` → `ChangelogRelease`
+* `entriesByType` → `Map<ChangelogEntry.Type, List<ChangelogEntry>>`
 
-See xref:src/main/java/org/apache/logging/log4j/changelog/ChangelogRelease.java[ChangelogRelease.java] and xref:src/main/java/org/apache/logging/log4j/changelog/ChangelogEntry.java[ChangelogEntry.java] for details.
+See xref:src/main/java/org/apache/logging/log4j/changelog/ChangelogRelease.java[`ChangelogRelease`] and xref:src/main/java/org/apache/logging/log4j/changelog/ChangelogEntry.java[`ChangelogEntry`] for details.
 
+These templates are generally used to generate release notes for a particular release.
 A sample changelog template file is shared below.
 
-.`src/changelog/2.19.0/.changelog.adoc.ftl` file contents
+.`src/changelog/2.19.0/.release-notes.adoc.ftl` file contents
 [source,asciidoc]
 ----
 = ${release.version}<#if release.date?has_content> (${release.date})</#if>
 
-Changes staged for the next version that is yet to be released.
+This release primarily contains bug fixes and minor enhancements.
 
 <#if entriesByType?size gt 0>== Changes
 <#list entriesByType as entryType, entries>
@@ -227,16 +186,16 @@ by <#list entry.authors as author><#if author.name?has_content>${author.name}<#e
 </#if>
 ----
 
-[#index-template-file]
-== Index template file
+[#index-template]
+=== Index templates
 
-`.index.adoc.ftl` FreeMarker templated AsciiDoc file is compiled by `ChangelogExporter` with the following input data hash:
+Index template files (e.g., `src/changelog/.index.adoc.ftl`) are rendered with the release information of all releases using the following input data hash:
 
 * `releases` -> list of hashes containing following keys:
 ** `version`
 ** `date`
-** `changelogFileName`
 
+These template files are generally used to generate the index page referencing to release notes of each release.
 A sample index template file is shared below.
 
 .`src/changelog/.index.adoc.ftl` file contents
@@ -245,7 +204,7 @@ A sample index template file is shared below.
 = Release changelogs
 
 <#list releases as release>
-* xref:${release.changelogFileName}[${release.version}]<#if release.date?has_content> (${release.date})</#if>
+* xref:${release.version}.adoc[${release.version}]<#if release.date?has_content> (${release.date})</#if>
 </#list>
 ----
 
@@ -259,18 +218,17 @@ You have just committed, or better, about to commit a great feature you have bee
 Simply create a <<#changelog-entry-file>> and commit it along with your change!
 
 [#qa-generate]
-=== How can I export changelogs to AsciiDoc files?
+=== How can I export changelogs to AsciiDoc, Markdown, etc. files?
 
-You need to use `ChangelogExporter`, which is accessible through xref:../log4j-changelog-maven-plugin/README.adoc#export[the `export` goal the Maven plugin].
-It exports the entire changelog directory (e.g., `src/changelog`) using `.index.adoc.ftl` and `.changelog.adoc.ftl` template files.
+You need to use xref:../log4j-changelog-maven-plugin/README.adoc#export[the `export` goal the Maven plugin].
 
 [#qa-deploy-release]
-=== I am about to deploy a new Log4j release. What shall I do?
+=== I am about to deploy a new release. What shall I do?
 
 Just before a release, three things need to happen in the changelog sources:
 
 . *Changelog entry files needs to be moved* from the _upcoming_ release changelog directory `<changelogDirectory>/.<releaseVersionMajor>.x.x`  to the _new_ release changelog directory `<changelogDirectory>/<releaseVersion>`
-. *`.changelog.adoc.ftl` needs to be copied* from the _upcoming_ release changelog directory to the _new_ release changelog directory, unless it already exists in the target
+. *Templates need to be copied* from the _upcoming_ release changelog directory to the _new_ release changelog directory, unless it already exists in the target
 . *`.release.xml` needs to be created* in the _new_ release changelog directory
 
 Due to the nature of release candidates, above steps might need to be repeated multiple times.
@@ -281,7 +239,7 @@ Log4j _releases_ and _release candidates_ all get deployed to the same https://r
 Their `pom.xml` files all contain the same release version, e.g., `2.19.0`.
 There are no `-rc1`, `-rc2`, etc. suffixes in the version of a release candidate.
 Once a release candidate voting reaches to a consensus for release, associated artifacts simply get promoted from the _staging_ to the _public_ repository.
-Hence, there are no differences between releases and release candidates.
+Hence, there are no differences between releases and release candidates from the point of view of changelogs.
 ====
 
 How to carry out aforementioned changes are explained below in steps:
@@ -295,12 +253,12 @@ How to carry out aforementioned changes are explained below in steps:
     -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
+. Verify that `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) is created, and it contains templates, changelog entry files, and a `.release.xml`
 +
 [IMPORTANT]
 ====
-If `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) already exists with certain content, `ChangelogReleaser` will only move new changelog entry files and override `.release.xml`; `.changelog.adoc.ftl` will not be touched, if it already exists.
+If `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) already exists with certain content, `ChangelogReleaser` will only move new changelog entry files and override `.release.xml`; templates will not be overridden.
 This allows one to run `ChangelogReleaser` multiple times, e.g., to incorporate changes added to a release candidate.
 ====
-. Edit the populated `.changelog.adoc.ftl`
+. Edit the populated templates (e.g., update the release notes with a short summary paragraph)
 . `git add` the changes in the changelog directory (e.g., `/src/changelog`) and commit them
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 df35b37..ec97bcb 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
@@ -52,10 +52,6 @@ public final class ChangelogFiles {
                 .collect(Collectors.toSet()));
     }
 
-    public static Path indexTemplateFile(final Path changelogDirectory) {
-        return changelogDirectory.resolve(".index.adoc.ftl");
-    }
-
     public static Path releaseDirectory(final Path changelogDirectory, final String releaseVersion) {
         return changelogDirectory.resolve(releaseVersion);
     }
@@ -64,8 +60,8 @@ public final class ChangelogFiles {
         return releaseDirectory.resolve(".release.xml");
     }
 
-    public static Path releaseChangelogTemplateFile(final Path releaseDirectory) {
-        return releaseDirectory.resolve(".changelog.adoc.ftl");
+    public static String templateFileNameExtension() {
+        return "ftl";
     }
 
 }
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 bca1f6d..1a3d6ae 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
@@ -17,8 +17,6 @@
 package org.apache.logging.log4j.changelog.exporter;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.*;
@@ -60,7 +58,6 @@ public final class ChangelogExporter {
             for (int releaseIndex = 0; releaseIndex < releaseDirectories.size(); releaseIndex++) {
                 final Path releaseDirectory = releaseDirectories.get(releaseIndex);
                 final ChangelogRelease changelogRelease = changelogReleases.get(releaseIndex);
-                final Path releaseChangelogTemplateFile = ChangelogFiles.releaseChangelogTemplateFile(releaseDirectory);
                 final int changelogEntryCount;
                 try {
                     changelogEntryCount = exportRelease(
@@ -68,7 +65,7 @@ public final class ChangelogExporter {
                             args.changelogDirectory,
                             releaseDirectory,
                             changelogRelease,
-                            releaseChangelogTemplateFile);
+                            args.changelogTemplates);
                 } catch (final Exception error) {
                     final String message =
                             String.format("failed exporting release from directory `%s`", releaseDirectory);
@@ -98,15 +95,13 @@ public final class ChangelogExporter {
                     final Path upcomingReleaseDirectory =
                             ChangelogFiles.unreleasedDirectory(args.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,
                             upcomingReleaseDirectory,
                             upcomingRelease,
-                            upcomingReleaseChangelogTemplateFile);
+                            args.changelogTemplates);
                     changelogReleases.add(upcomingRelease);
                     changelogEntryCounts.add(changelogEntryCount);
                 });
@@ -115,6 +110,7 @@ public final class ChangelogExporter {
         exportIndex(
                 args.outputDirectory,
                 args.changelogDirectory,
+                args.indexTemplates,
                 changelogReleases,
                 changelogEntryCounts);
 
@@ -144,19 +140,20 @@ public final class ChangelogExporter {
             final Path changelogDirectory,
             final Path releaseDirectory,
             final ChangelogRelease changelogRelease,
-            final Path releaseChangelogTemplateFile) {
+            final Set<ChangelogExporterTemplate> changelogTemplates) {
         final Map<ChangelogEntry.Type, List<ChangelogEntry>> changelogEntriesByType =
                 readChangelogEntriesByType(releaseDirectory);
         try {
             exportRelease(
                     outputDirectory,
                     changelogDirectory,
+                    releaseDirectory,
                     changelogRelease,
                     changelogEntriesByType,
-                    releaseChangelogTemplateFile);
-        } catch (final IOException error) {
+                    changelogTemplates);
+        } catch (final Exception error) {
             final String message = String.format("failed exporting release from directory `%s`", releaseDirectory);
-            throw new UncheckedIOException(message, error);
+            throw new RuntimeException(message, error);
         }
         return changelogEntriesByType
                 .values()
@@ -181,21 +178,30 @@ public final class ChangelogExporter {
     private static void exportRelease(
             final Path outputDirectory,
             final Path changelogDirectory,
+            final Path releaseDirectory,
             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);
-        final String releaseChangelogTemplateName = templateName(changelogDirectory, releaseChangelogTemplateFile);
-        FreeMarkerUtils.render(
-                changelogDirectory,
-                releaseChangelogTemplateName,
-                releaseChangelogTemplateData,
-                releaseChangelogFile);
+            final Set<ChangelogExporterTemplate> changelogTemplates) {
+        final Map<String, Object> changelogTemplateData = new LinkedHashMap<>();
+        changelogTemplateData.put("release", release);
+        changelogTemplateData.put("entriesByType", entriesByType);
+        for (final ChangelogExporterTemplate changelogTemplate : changelogTemplates) {
+            final Path changelogTemplateSourceFile = releaseDirectory.resolve(changelogTemplate.sourceFileName);
+            if (Files.exists(changelogTemplateSourceFile)) {
+                final String changelogTemplateName = templateName(changelogDirectory, changelogTemplateSourceFile);
+                final String changelogTemplateTargetFileName =
+                        changelogTemplate.targetFileName.replaceAll("%v", release.version);
+                final Path changelogTemplateTargetFile = outputDirectory.resolve(changelogTemplateTargetFileName);
+                FreeMarkerUtils.render(
+                        changelogDirectory,
+                        changelogTemplateName,
+                        changelogTemplateData,
+                        changelogTemplateTargetFile);
+            } else if (changelogTemplate.failIfNotFound) {
+                final String message = String.format("could not find template file: `%s`", changelogTemplateSourceFile);
+                throw new IllegalStateException(message);
+            }
+        }
     }
 
     private static ChangelogRelease upcomingRelease(final int versionMajor) {
@@ -206,6 +212,7 @@ public final class ChangelogExporter {
     private static void exportIndex(
             final Path outputDirectory,
             final Path changelogDirectory,
+            final Set<ChangelogExporterTemplate> indexTemplates,
             final List<ChangelogRelease> changelogReleases,
             final List<Integer> changelogEntryCounts) {
         final Object indexTemplateData = Collections.singletonMap(
@@ -220,19 +227,24 @@ public final class ChangelogExporter {
                             changelogReleaseData.put("version", changelogRelease.version);
                             changelogReleaseData.put("date", changelogRelease.date);
                             changelogReleaseData.put("changelogEntryCount", changelogEntryCount);
-                            changelogReleaseData.put("changelogFileName", releaseChangelogFileName(changelogRelease));
                             return (Object) changelogReleaseData;
                         })
                         .collect(Collectors.toList()));
-        final Path indexTemplateFile = ChangelogFiles.indexTemplateFile(changelogDirectory);
-        final String indexTemplateName = templateName(changelogDirectory, indexTemplateFile);
-        final Path indexFile = outputDirectory.resolve("index.adoc");
-        FreeMarkerUtils.render(changelogDirectory, indexTemplateName, indexTemplateData, indexFile);
-    }
-
-    private static String releaseChangelogFileName(final ChangelogRelease changelogRelease) {
-        // Using only the version (that is, avoiding the date) in the filename so that one can determine the link to the changelog of a particular release with only version information
-        return String.format("%s.adoc", changelogRelease.version);
+        indexTemplates.forEach(indexTemplate -> {
+            final Path indexTemplateSourceFile = changelogDirectory.resolve(indexTemplate.sourceFileName);
+            if (Files.exists(indexTemplateSourceFile)) {
+                final String indexTemplateSourceName = templateName(changelogDirectory, indexTemplateSourceFile);
+                final Path indexTemplateTargetFile = outputDirectory.resolve(indexTemplate.targetFileName);
+                FreeMarkerUtils.render(
+                        changelogDirectory,
+                        indexTemplateSourceName,
+                        indexTemplateData,
+                        indexTemplateTargetFile);
+            } else if (indexTemplate.failIfNotFound) {
+                final String message = String.format("could not find template file: `%s`", indexTemplateSourceFile);
+                throw new IllegalStateException(message);
+            }
+        });
     }
 
     /**
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 6de9236..6b25147 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
@@ -18,15 +18,26 @@ package org.apache.logging.log4j.changelog.exporter;
 
 import java.nio.file.Path;
 import java.util.Objects;
+import java.util.Set;
 
 public final class ChangelogExporterArgs {
 
     final Path changelogDirectory;
 
+    final Set<ChangelogExporterTemplate> indexTemplates;
+
+    final Set<ChangelogExporterTemplate> changelogTemplates;
+
     final Path outputDirectory;
 
-    public ChangelogExporterArgs(final Path changelogDirectory, final Path outputDirectory) {
+    public ChangelogExporterArgs(
+            final Path changelogDirectory,
+            final Set<ChangelogExporterTemplate> indexTemplates,
+            final Set<ChangelogExporterTemplate> changelogTemplates,
+            final Path outputDirectory) {
         this.changelogDirectory = Objects.requireNonNull(changelogDirectory, "changelogDirectory");
+        this.indexTemplates = Objects.requireNonNull(indexTemplates, "indexTemplates");
+        this.changelogTemplates = Objects.requireNonNull(changelogTemplates, "changelogTemplates");
         this.outputDirectory = Objects.requireNonNull(outputDirectory, "outputDirectory");
     }
 
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterTemplate.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterTemplate.java
new file mode 100644
index 0000000..8558a3b
--- /dev/null
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/ChangelogExporterTemplate.java
@@ -0,0 +1,83 @@
+/*
+ * 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.exporter;
+
+import java.util.Objects;
+
+import org.apache.logging.log4j.changelog.ChangelogFiles;
+
+public final class ChangelogExporterTemplate {
+
+    final String sourceFileName;
+
+    final String targetFileName;
+
+    final boolean failIfNotFound;
+
+    public ChangelogExporterTemplate(
+            final String sourceFileName,
+            final String targetFileName,
+            final boolean failIfNotFound) {
+        this.sourceFileName = requireTemplateFileName(sourceFileName, "sourceFileName");
+        this.targetFileName = Objects.requireNonNull(targetFileName, "targetFileName");
+        this.failIfNotFound = failIfNotFound;
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    private static String requireTemplateFileName(final String fileName, final String fieldName) {
+        Objects.requireNonNull(fileName, fieldName);
+        final String templateFileNameSuffix = '.' + ChangelogFiles.templateFileNameExtension();
+        if (!fileName.endsWith(templateFileNameSuffix)) {
+            final String message = String.format(
+                    "`%s` contains file name without a `%s` suffix: `%s`",
+                    fieldName,
+                    templateFileNameSuffix,
+                    fileName);
+            throw new IllegalArgumentException(message);
+        }
+        return fileName;
+    }
+
+    @Override
+    public boolean equals(Object instance) {
+        if (this == instance) {
+            return true;
+        }
+        if (instance == null || getClass() != instance.getClass()) {
+            return false;
+        }
+        ChangelogExporterTemplate template = (ChangelogExporterTemplate) instance;
+        return failIfNotFound == template.failIfNotFound &&
+                sourceFileName.equals(template.sourceFileName) &&
+                targetFileName.equals(template.targetFileName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(sourceFileName, targetFileName, failIfNotFound);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "`%s` → `%s`%s",
+                sourceFileName,
+                targetFileName,
+                failIfNotFound ? " (failIfNotFound)" : "");
+    }
+
+}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/FreeMarkerUtils.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/FreeMarkerUtils.java
index 5ab86d6..76e0660 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/FreeMarkerUtils.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/exporter/FreeMarkerUtils.java
@@ -77,8 +77,9 @@ final class FreeMarkerUtils {
             }
         } catch (final Exception error) {
             final String message = String.format(
-                    "failed rendering template `%s` to file `%s`",
+                    "failed rendering template `%s` in directory `%s` to file `%s`",
                     templateName,
+                    templateDirectory,
                     outputFile);
             throw new RuntimeException(message, error);
         }
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 223ac68..c2230fe 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,8 @@ import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.LocalDate;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.changelog.ChangelogFiles;
 import org.apache.logging.log4j.changelog.ChangelogRelease;
@@ -125,14 +127,28 @@ public final class ChangelogReleaser {
             final Path unreleasedDirectory,
             final Path releaseDirectory)
             throws IOException {
-        final Path targetFile = ChangelogFiles.releaseChangelogTemplateFile(releaseDirectory);
-        if (Files.exists(targetFile)) {
-            System.out.format("keeping the existing changelog template file: `%s`%n", targetFile);
-        } else {
-            final Path sourceFile = ChangelogFiles.releaseChangelogTemplateFile(unreleasedDirectory);
-            System.out.format("moving the changelog template file `%s` to `%s`%n", sourceFile, targetFile);
-            Files.move(sourceFile, targetFile);
+        Set<String> releaseChangelogTemplateFileNames = releaseChangelogTemplateFileNames(unreleasedDirectory);
+        for (final String releaseChangelogTemplateFileName : releaseChangelogTemplateFileNames) {
+            final Path targetFile = releaseDirectory.resolve(releaseChangelogTemplateFileName);
+            if (Files.exists(targetFile)) {
+                System.out.format("keeping the existing changelog template file: `%s`%n", targetFile);
+            } else {
+                final Path sourceFile = unreleasedDirectory.resolve(releaseChangelogTemplateFileName);
+                System.out.format("moving the changelog template file `%s` to `%s`%n", sourceFile, targetFile);
+                Files.move(sourceFile, targetFile);
+            }
         }
     }
 
+    private static Set<String> releaseChangelogTemplateFileNames(final Path releaseDirectory) {
+        final String templateFileNameSuffix = '.' + ChangelogFiles.templateFileNameExtension();
+        return FileUtils.findAdjacentFiles(
+                releaseDirectory,
+                false,
+                paths -> paths
+                        .filter(path -> path.endsWith(templateFileNameSuffix))
+                        .map(path -> path.getFileName().toString())
+                        .collect(Collectors.toSet()));
+    }
+
 }
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 cbdad83..d49ddfe 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
@@ -32,18 +32,9 @@ public final class ChangelogReleaserArgs {
             final Path changelogDirectory,
             final String releaseVersion,
             final LocalDate releaseDate) {
-
-        // Check arguments
-        Objects.requireNonNull(changelogDirectory, "changelogDirectory");
-        Objects.requireNonNull(releaseVersion, "releaseVersion");
-        VersionUtils.requireSemanticVersioning(releaseVersion, "releaseVersion");
-        Objects.requireNonNull(releaseDate, "releaseDate");
-
-        // Set fields
-        this.changelogDirectory = changelogDirectory;
-        this.releaseVersion = releaseVersion;
-        this.releaseDate = releaseDate;
-
+        this.changelogDirectory = Objects.requireNonNull(changelogDirectory, "changelogDirectory");
+        this.releaseVersion = VersionUtils.requireSemanticVersioning(releaseVersion, "releaseVersion");
+        this.releaseDate = Objects.requireNonNull(releaseDate, "releaseDate");
     }
 
 }
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/VersionUtils.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/VersionUtils.java
index 08dc06b..bfa519f 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/VersionUtils.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/changelog/releaser/VersionUtils.java
@@ -16,19 +16,23 @@
  */
 package org.apache.logging.log4j.changelog.releaser;
 
+import java.util.Objects;
+
 final class VersionUtils {
 
     private static final String VERSION_PATTERN = "^\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?$";
 
     private VersionUtils() {}
 
-    static void requireSemanticVersioning(final String version, final String name) {
+    static String requireSemanticVersioning(final String version, final String fieldName) {
+        Objects.requireNonNull(version, fieldName);
         if (!version.matches(VERSION_PATTERN)) {
             final String message = String.format(
                     "provided version in `%s` does not match the expected `<major>.<minor>.<patch>[-SNAPSHOT]` pattern: `%s`",
-                    name, version);
+                    fieldName, version);
             throw new IllegalArgumentException(message);
         }
+        return version;
     }
 
     static int versionMajor(final String version) {
diff --git a/log4j-changelog/src/main/resources/log4j-changelog.xsd b/log4j-changelog/src/main/resources/log4j-changelog.xsd
index ee532ea..40ce80d 100644
--- a/log4j-changelog/src/main/resources/log4j-changelog.xsd
+++ b/log4j-changelog/src/main/resources/log4j-changelog.xsd
@@ -19,7 +19,7 @@
         targetNamespace="http://logging.apache.org/log4j/changelog"
         xmlns:cl="http://logging.apache.org/log4j/changelog"
         elementFormDefault="qualified"
-        version="0.1.0">
+        version="0.1.1">
 
   <element name="release">
     <complexType>
@@ -74,7 +74,7 @@
   <complexType name="descriptionType">
     <simpleContent>
       <extension base="string">
-        <attribute name="format" type="string" use="required" fixed="asciidoc"/>
+        <attribute name="format" type="string" use="required"/>
       </extension>
     </simpleContent>
   </complexType>
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..679c113 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
@@ -18,9 +18,13 @@ package org.apache.logging.log4j.changelog;
 
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.logging.log4j.changelog.exporter.ChangelogExporter;
 import org.apache.logging.log4j.changelog.exporter.ChangelogExporterArgs;
+import org.apache.logging.log4j.changelog.exporter.ChangelogExporterTemplate;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.CleanupMode;
@@ -32,11 +36,22 @@ class ChangelogExporterTest {
 
     @Test
     void output_should_match(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path outputDirectory) {
-        ChangelogExporterArgs args = new ChangelogExporterArgs(
+        final ChangelogExporterArgs args = new ChangelogExporterArgs(
                 Paths.get("src/test/resources/3-enriched"),
+                setOf(
+                        new ChangelogExporterTemplate(".index.adoc.ftl", "index.adoc", true),
+                        new ChangelogExporterTemplate(".index.txt.ftl", "index.adoc", false)),
+                setOf(
+                        new ChangelogExporterTemplate(".release-notes.adoc.ftl", "%v.adoc", true),
+                        new ChangelogExporterTemplate(".release-notes.txt.ftl", "%v.txt", false)),
                 outputDirectory);
         ChangelogExporter.performExport(args);
         assertDirectoryContentMatches(outputDirectory, Paths.get("src/test/resources/4-exported"));
     }
 
+    @SafeVarargs
+    private static <V> Set<V> setOf(V... values) {
+        return Stream.of(values).collect(Collectors.toSet());
+    }
+
 }
diff --git a/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogReleaserTest.java b/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogReleaserTest.java
index 1aebe7b..3344172 100644
--- a/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogReleaserTest.java
+++ b/log4j-changelog/src/test/java/org/apache/logging/log4j/changelog/ChangelogReleaserTest.java
@@ -43,7 +43,7 @@ class ChangelogReleaserTest {
         copyDirectory(new File("src/test/resources/3-enriched"), changelogDirectory.toFile());
 
         // Invoke the releaser
-        ChangelogReleaserArgs args = new ChangelogReleaserArgs(
+        final ChangelogReleaserArgs args = new ChangelogReleaserArgs(
                 changelogDirectory,
                 "2.19.0",
                 LocalDate.parse("2023-01-25"));
diff --git a/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.adoc.ftl
similarity index 88%
rename from log4j-changelog/src/test/resources/3-enriched/.2.x.x/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.adoc.ftl
index 55dd1ed..baa52d9 100644
--- a/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.adoc.ftl
@@ -16,7 +16,6 @@
 ////
 
 = ${release.version}<#if release.date?has_content> (${release.date})</#if>
+<#include ".release-notes.common.ftl">
 
-Changes staged for the next 2.x.x version that is yet to be released.
-
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.common.ftl b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.common.ftl
new file mode 100644
index 0000000..c0d38f7
--- /dev/null
+++ b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.common.ftl
@@ -0,0 +1,18 @@
+<#--
+  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.
+-->
+
+Changes staged for the next 2.x.x version that is yet to be released.
diff --git a/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.txt.ftl b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.txt.ftl
new file mode 100644
index 0000000..122eabd
--- /dev/null
+++ b/log4j-changelog/src/test/resources/3-enriched/.2.x.x/.release-notes.txt.ftl
@@ -0,0 +1,21 @@
+<#--
+  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.
+-->
+
+### ${release.version}<#if release.date?has_content> (${release.date})</#if> ###
+<#include ".release-notes.common.ftl">
+
+<#include "../.changelog.txt.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/.3.x.x/.release-notes.adoc.ftl
similarity index 95%
copy from log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl
copy to log4j-changelog/src/test/resources/3-enriched/.3.x.x/.release-notes.adoc.ftl
index b2d69db..95a0397 100644
--- a/log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/.3.x.x/.release-notes.adoc.ftl
@@ -19,4 +19,4 @@
 
 Changes staged for the next 3.x.x version that is yet to be released.
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/.4.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/.4.x.x/.release-notes.adoc.ftl
similarity index 95%
rename from log4j-changelog/src/test/resources/5-released/.4.x.x/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/3-enriched/.4.x.x/.release-notes.adoc.ftl
index c4097db..c01f82f 100644
--- a/log4j-changelog/src/test/resources/5-released/.4.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/.4.x.x/.release-notes.adoc.ftl
@@ -19,4 +19,4 @@
 
 Changes staged for the next 4.x.x version that is yet to be released.
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/.changelog.adoc.ftl
similarity index 98%
rename from log4j-changelog/src/test/resources/3-enriched/.changelog-entries.adoc.ftl
rename to log4j-changelog/src/test/resources/3-enriched/.changelog.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.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/3-enriched/.changelog.txt.ftl b/log4j-changelog/src/test/resources/3-enriched/.changelog.txt.ftl
new file mode 100644
index 0000000..fd135eb
--- /dev/null
+++ b/log4j-changelog/src/test/resources/3-enriched/.changelog.txt.ftl
@@ -0,0 +1,26 @@
+<#--
+  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.
+-->
+<#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.link}[${issue.id}]<#if issue?has_next>, </#if></#list>)
+</#list>
+</#list>
+</#if>
diff --git a/log4j-changelog/src/test/resources/3-enriched/.index.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/.index.adoc.ftl
index 27d0149..8be38da 100644
--- a/log4j-changelog/src/test/resources/3-enriched/.index.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/.index.adoc.ftl
@@ -15,8 +15,8 @@
     limitations under the License.
 ////
 
-= Release changelogs
+= Release notes
 
 <#list releases as release><#if release.changelogEntryCount gt 0>
-* xref:${release.changelogFileName}[${release.version}]<#if release.date?has_content> (${release.date})</#if>
+* xref:${release.version}.adoc[${release.version}]<#if release.date?has_content> (${release.date})</#if>
 </#if></#list>
diff --git a/log4j-changelog/src/test/resources/5-released/2.17.2/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/2.17.2/.release-notes.adoc.ftl
similarity index 98%
rename from log4j-changelog/src/test/resources/5-released/2.17.2/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/3-enriched/2.17.2/.release-notes.adoc.ftl
index d32f334..bc0ffc4 100644
--- a/log4j-changelog/src/test/resources/5-released/2.17.2/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/2.17.2/.release-notes.adoc.ftl
@@ -44,4 +44,4 @@ Java 6 and Java 7 are no longer supported by the Log4j team.
 
 For complete information on Apache Log4j 2, including instructions on how to submit bug reports, patches, or suggestions for improvement, see http://logging.apache.org/log4j/2.x/[the Apache Log4j 2 website].
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/2.18.0/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/3-enriched/2.18.0/.release-notes.adoc.ftl
similarity index 97%
rename from log4j-changelog/src/test/resources/5-released/2.18.0/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/3-enriched/2.18.0/.release-notes.adoc.ftl
index 5fa0ffa..a388c95 100644
--- a/log4j-changelog/src/test/resources/5-released/2.18.0/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/3-enriched/2.18.0/.release-notes.adoc.ftl
@@ -33,4 +33,4 @@ Java 6 and Java 7 are no longer supported by the Log4j team.
 
 For complete information on Apache Log4j 2, including instructions on how to submit bug reports, patches, or suggestions for improvement, see http://logging.apache.org/log4j/2.x/[the Apache Log4j 2 website].
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/4-exported/2.17.2.adoc b/log4j-changelog/src/test/resources/4-exported/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/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/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/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/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/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/2.x.x.txt b/log4j-changelog/src/test/resources/4-exported/2.x.x.txt
new file mode 100644
index 0000000..a2d992c
--- /dev/null
+++ b/log4j-changelog/src/test/resources/4-exported/2.x.x.txt
@@ -0,0 +1,15 @@
+
+### 2.x.x ###
+
+Changes staged for the next 2.x.x version that is yet to be released.
+
+=== Changes
+
+--- Changed
+
+* Add `getExplicitLevel` method to `LoggerConfig` (https://issues.apache.org/jira/browse/LOG4J2-3572[LOG4J2-3572])
+
+--- Fixed
+
+* Make `StatusConsoleListener` use `SimpleLogger` internally (https://issues.apache.org/jira/browse/LOG4J2-3584[LOG4J2-3584])
+* Harden `InstantFormatter` against delegate failures (https://issues.apache.org/jira/browse/LOG4J2-3614[LOG4J2-3614])
diff --git a/log4j-changelog/src/test/resources/4-exported/3.x.x.adoc b/log4j-changelog/src/test/resources/4-exported/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/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/index.adoc b/log4j-changelog/src/test/resources/4-exported/index.adoc
index a827e27..5ab1053 100644
--- a/log4j-changelog/src/test/resources/4-exported/index.adoc
+++ b/log4j-changelog/src/test/resources/4-exported/index.adoc
@@ -15,7 +15,7 @@
     limitations under the License.
 ////
 
-= Release changelogs
+= Release notes
 
 * xref:3.x.x.adoc[3.x.x]
 * xref:2.x.x.adoc[2.x.x]
diff --git a/log4j-changelog/src/test/resources/3-enriched/.3.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/.3.x.x/.release-notes.adoc.ftl
similarity index 95%
rename from log4j-changelog/src/test/resources/3-enriched/.3.x.x/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/.3.x.x/.release-notes.adoc.ftl
index b2d69db..95a0397 100644
--- a/log4j-changelog/src/test/resources/3-enriched/.3.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/.3.x.x/.release-notes.adoc.ftl
@@ -19,4 +19,4 @@
 
 Changes staged for the next 3.x.x version that is yet to be released.
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/3-enriched/.4.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/.4.x.x/.release-notes.adoc.ftl
similarity index 95%
rename from log4j-changelog/src/test/resources/3-enriched/.4.x.x/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/.4.x.x/.release-notes.adoc.ftl
index c4097db..c01f82f 100644
--- a/log4j-changelog/src/test/resources/3-enriched/.4.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/.4.x.x/.release-notes.adoc.ftl
@@ -19,4 +19,4 @@
 
 Changes staged for the next 4.x.x version that is yet to be released.
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl b/log4j-changelog/src/test/resources/5-released/.changelog.adoc.ftl
similarity index 98%
rename from log4j-changelog/src/test/resources/5-released/.changelog-entries.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/.changelog.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.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/5-released/.changelog.txt.ftl b/log4j-changelog/src/test/resources/5-released/.changelog.txt.ftl
new file mode 100644
index 0000000..fd135eb
--- /dev/null
+++ b/log4j-changelog/src/test/resources/5-released/.changelog.txt.ftl
@@ -0,0 +1,26 @@
+<#--
+  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.
+-->
+<#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.link}[${issue.id}]<#if issue?has_next>, </#if></#list>)
+</#list>
+</#list>
+</#if>
diff --git a/log4j-changelog/src/test/resources/5-released/.index.adoc.ftl b/log4j-changelog/src/test/resources/5-released/.index.adoc.ftl
index 27d0149..8be38da 100644
--- a/log4j-changelog/src/test/resources/5-released/.index.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/.index.adoc.ftl
@@ -15,8 +15,8 @@
     limitations under the License.
 ////
 
-= Release changelogs
+= Release notes
 
 <#list releases as release><#if release.changelogEntryCount gt 0>
-* xref:${release.changelogFileName}[${release.version}]<#if release.date?has_content> (${release.date})</#if>
+* xref:${release.version}.adoc[${release.version}]<#if release.date?has_content> (${release.date})</#if>
 </#if></#list>
diff --git a/log4j-changelog/src/test/resources/3-enriched/2.17.2/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/2.17.2/.release-notes.adoc.ftl
similarity index 98%
rename from log4j-changelog/src/test/resources/3-enriched/2.17.2/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/2.17.2/.release-notes.adoc.ftl
index d32f334..bc0ffc4 100644
--- a/log4j-changelog/src/test/resources/3-enriched/2.17.2/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/2.17.2/.release-notes.adoc.ftl
@@ -44,4 +44,4 @@ Java 6 and Java 7 are no longer supported by the Log4j team.
 
 For complete information on Apache Log4j 2, including instructions on how to submit bug reports, patches, or suggestions for improvement, see http://logging.apache.org/log4j/2.x/[the Apache Log4j 2 website].
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/3-enriched/2.18.0/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/2.18.0/.release-notes.adoc.ftl
similarity index 97%
rename from log4j-changelog/src/test/resources/3-enriched/2.18.0/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/2.18.0/.release-notes.adoc.ftl
index 5fa0ffa..a388c95 100644
--- a/log4j-changelog/src/test/resources/3-enriched/2.18.0/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/2.18.0/.release-notes.adoc.ftl
@@ -33,4 +33,4 @@ Java 6 and Java 7 are no longer supported by the Log4j team.
 
 For complete information on Apache Log4j 2, including instructions on how to submit bug reports, patches, or suggestions for improvement, see http://logging.apache.org/log4j/2.x/[the Apache Log4j 2 website].
 
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/2.19.0/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/2.19.0/.changelog.adoc.ftl
deleted file mode 100644
index 55dd1ed..0000000
--- a/log4j-changelog/src/test/resources/5-released/2.19.0/.changelog.adoc.ftl
+++ /dev/null
@@ -1,22 +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
-
-         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.
-////
-
-= ${release.version}<#if release.date?has_content> (${release.date})</#if>
-
-Changes staged for the next 2.x.x version that is yet to be released.
-
-<#include "../.changelog-entries.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.adoc.ftl
similarity index 88%
rename from log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl
rename to log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.adoc.ftl
index b2d69db..baa52d9 100644
--- a/log4j-changelog/src/test/resources/5-released/.3.x.x/.changelog.adoc.ftl
+++ b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.adoc.ftl
@@ -16,7 +16,6 @@
 ////
 
 = ${release.version}<#if release.date?has_content> (${release.date})</#if>
+<#include ".release-notes.common.ftl">
 
-Changes staged for the next 3.x.x version that is yet to be released.
-
-<#include "../.changelog-entries.adoc.ftl">
+<#include "../.changelog.adoc.ftl">
diff --git a/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.common.ftl b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.common.ftl
new file mode 100644
index 0000000..c0d38f7
--- /dev/null
+++ b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.common.ftl
@@ -0,0 +1,18 @@
+<#--
+  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.
+-->
+
+Changes staged for the next 2.x.x version that is yet to be released.
diff --git a/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.txt.ftl b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.txt.ftl
new file mode 100644
index 0000000..122eabd
--- /dev/null
+++ b/log4j-changelog/src/test/resources/5-released/2.19.0/.release-notes.txt.ftl
@@ -0,0 +1,21 @@
+<#--
+  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.
+-->
+
+### ${release.version}<#if release.date?has_content> (${release.date})</#if> ###
+<#include ".release-notes.common.ftl">
+
+<#include "../.changelog.txt.ftl">
diff --git a/log4j-tools-parent/pom.xml b/log4j-tools-parent/pom.xml
index ab77989..9c41208 100644
--- a/log4j-tools-parent/pom.xml
+++ b/log4j-tools-parent/pom.xml
@@ -201,6 +201,7 @@
             <exclude>**/target/**/*</exclude>
             <exclude>.java-version</exclude>
             <exclude>.mvn/jvm.config</exclude>
+            <exclude>**/*.txt</exclude>
             <!-- IntelliJ IDEA files: -->
             <exclude>.idea/**/*</exclude>
             <exclude>**/*.iml</exclude>