diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 000000000..1490bfb3a
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "docfx": {
+ "version": "2.81.0",
+ "commands": [
+ "docfx"
+ ],
+ "rollForward": false
+ }
+ }
+}
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index dbf190268..4ba411e81 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -58,6 +58,14 @@ jobs:
run: |
dotnet test ./src/log4net.sln
+ # Recompiles both shipping projects through docfx with warnings as errors, so a broken doc
+ # comment or cref fails the pull request rather than the release build. The script itself
+ # is covered on all three runners by the Pester suite below; the real docfx run needs one.
+ - name: API reference
+ if: matrix.os == 'ubuntu-latest'
+ shell: pwsh
+ run: ./scripts/build-api-docs.ps1
+
# Pester 5.9 ships on all three runner images, so nothing is installed here.
- name: Release script tests
shell: pwsh
diff --git a/.gitignore b/.gitignore
index 48e60e698..45f91a147 100644
--- a/.gitignore
+++ b/.gitignore
@@ -218,6 +218,10 @@ _Pvt_Extensions
# Maven target dir created during site build
target
+# Metadata generated by scripts/build-api-docs.ps1
+/src/site/docfx/log4net/
+/src/site/docfx/log4net.Ext.Mail/
+
# other files to ignore
old-log4net.snk
src/GeneratedAssemblyInfo.cs
diff --git a/AGENTS.md b/AGENTS.md
index b81cf63d7..a2ba9cc9d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -256,15 +256,28 @@ The manual lives in `src/site/antora/modules/ROOT/pages/`. A new appender page n
not one: the page itself, an `xref` line in `nav.adoc` (kept alphabetical), and the appender table
in `manual/configuration/appenders.adoc`.
+The API reference is DocFX output from the XML doc comments, built by `scripts/build-api-docs.ps1`
+into `target/site/api` and in CI with warnings as errors, so an unresolved `cref` or a missing
+public doc comment fails the PR. The configuration is in `src/site/docfx/`; the `log4net` and
+`log4net.Ext.Mail` folders generated there are ignored. `log4net` is documented as `net462`, since
+the `NET462_OR_GREATER` types exist only there. The Ext.Mail entry sets `ProduceReferenceAssembly`,
+without which Roslyn cannot match its project reference to log4net and warns.
+
## Release scripts
+- **Before writing a helper function in a script, grep `scripts/` for its name and its idiom**
+ (`Push-Location`, `Remove-Item`, `Get-Date`). A function two scripts need lives in
+ `scripts/Common.psm1`, imported with `Import-Module $PSScriptRoot/Common.psm1 -Force`, not
+ copied into the second script. A module takes its preferences from the global scope, so the
+ module sets `$ErrorActionPreference` itself.
- **Every file `build-release.ps1` writes gets LF on every platform.** `Set-Content` writes
`[Environment]::NewLine`; use `-NoNewline` with the lines joined by `` `n ``. A CR is invisible in
an editor, and `sha512sum` on macOS reads it as part of the file name.
- **Each release ships the verifier built with it**, so never add backward compatibility to
`verify-release.ps1` or `.sh`: a compatibility skip path is a hole, not a courtesy.
- The scripts are tested with Pester through `scripts/FakeCommands.TestHelper.ps1`, which shadows
- `dotnet`, `git`, `gpg`, `zip` and `mvnw` with shims on a prepended `PATH`, because Pester `Mock`
+ `dotnet`, `git`, `gpg`, `zip`, `mvnw` and `curl` with shims on a prepended `PATH` (`FAKE_FAIL` names a
+ command, or a command and its first argument such as `dotnet docfx`), because Pester `Mock`
intercepts only functions and cmdlets. CI runs the suite on macOS, Ubuntu and Windows, so keep
the helper's `$IsWindows` branch working and put byte-level assertions where all three see them.
diff --git a/antora-playbook.yaml b/antora-playbook.yaml
index c8951d3f7..b5afa7e40 100644
--- a/antora-playbook.yaml
+++ b/antora-playbook.yaml
@@ -115,6 +115,9 @@ ui:
+///
/// create table dbo.Log
/// (
/// Id bigint identity (1, 1) not null,
@@ -81,7 +81,7 @@ namespace log4net.Appender;
///
+///
///
///
@@ -131,7 +131,7 @@ namespace log4net.Appender;
///
///
///
-///
+/// ]]>
/// "DSN=MS Access Database;UID=admin;PWD=;SystemDB=C:\data\System.mdw;SafeTransactions = 0;FIL=MS Access;DriverID = 25;DBQ=C:\data\train33.mdb"
/// "Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\Work\cvs_root\log4net-1.2\access.mdb;UID=;PWD=;"
/// "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Work\cvs_root\log4net-1.2\access.mdb;User Id=;Password=;"
/// System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
/// System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
/// Microsoft.Data.Odbc.OdbcConnection,Microsoft.Data.Odbc,version=1.0.3300.0,publicKeyToken=b77a5c561934e089,culture=neutral
/// This is an optional package that you can download from
/// http://msdn.microsoft.com/downloads
/// search for ODBC .NET Data Provider.
/// System.Data.OracleClient.OracleConnection, System.Data.OracleClient, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
/// This is an optional package that you can download from
/// http://msdn.microsoft.com/downloads
diff --git a/src/log4net/Appender/AnsiColorTerminalAppender.cs b/src/log4net/Appender/AnsiColorTerminalAppender.cs
index 1c65d027d..142750aec 100644
--- a/src/log4net/Appender/AnsiColorTerminalAppender.cs
+++ b/src/log4net/Appender/AnsiColorTerminalAppender.cs
@@ -55,7 +55,7 @@ namespace log4net.Appender;
/// When configuring the ANSI colored terminal appender, a mapping should be
/// specified to map a logging level to a color. For example:
///
-///
+///
///
///
@@ -66,7 +66,7 @@ namespace log4net.Appender;
///
///
///
-///
+/// ]]>
///
+///
///
///
@@ -62,7 +62,7 @@ namespace log4net.Appender;
///
///
///
-///
+/// ]]>
///
+///
/// <mapping>
/// <level value="ERROR" />
/// <eventLogEntryType value="Error" />
diff --git a/src/log4net/Appender/ManagedColoredConsoleAppender.cs b/src/log4net/Appender/ManagedColoredConsoleAppender.cs
index 31d79233e..64b09c995 100644
--- a/src/log4net/Appender/ManagedColoredConsoleAppender.cs
+++ b/src/log4net/Appender/ManagedColoredConsoleAppender.cs
@@ -61,7 +61,7 @@ namespace log4net.Appender;
/// When configuring the colored console appender, mappings should be
/// specified to map logging levels to colors. For example:
///
-///
+///
///
///
@@ -79,7 +79,7 @@ namespace log4net.Appender;
///
///
///
-///
+/// ]]>
///
/// The Level is the standard log4net logging level while
/// ForeColor and BackColor are the values of
diff --git a/src/log4net/Appender/UdpAppender.cs b/src/log4net/Appender/UdpAppender.cs
index d0e755771..5ba6c89a2 100644
--- a/src/log4net/Appender/UdpAppender.cs
+++ b/src/log4net/Appender/UdpAppender.cs
@@ -49,7 +49,7 @@ namespace log4net.Appender;
/// This example shows how to log receive logging events that are sent
/// on IP address 244.0.0.1 and port 8080 to the console. The event is
/// encoded in the packet as a unicode string and it is decoded as such.
-///
+///
/// IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
/// UdpClient udpClient;
/// byte[] buffer;
@@ -71,7 +71,7 @@ namespace log4net.Appender;
/// Console.WriteLine(e.ToString());
/// }
///
-///
+///
/// Dim remoteEndPoint as IPEndPoint
/// Dim udpClient as UdpClient
/// Dim buffer as Byte()
@@ -94,13 +94,13 @@ namespace log4net.Appender;
/// An example configuration section to log information using this appender to the
/// IP 224.0.0.1 on port 8080:
///
-///
+///
///
///
///
///
-///
+/// ]]>
///
+///
///
///
@@ -41,7 +41,7 @@ namespace log4net.Config;
/// log4net configuration XML goes here
///
///
-///
+/// ]]>
///
+ ///
///
///
- ///
+ /// ]]>
///
///
+ ///
/// using log4net.Config;
/// using System.IO;
/// using System.Configuration;
@@ -210,13 +210,13 @@ public static ICollection Configure(XmlElement element)
///
/// In the .config file, the path to the log4net can be specified like this :
///
- ///
+ ///
///
///
///
///
- ///
+ /// ]]>
///
public static ICollection Configure(FileInfo configFile)
{
@@ -339,17 +339,17 @@ public static ICollection Configure(ILoggerRepository repository, XmlElement ele
/// configuration. If this file is also a .NET .config file then you must specify
/// a configuration section for the log4net element otherwise .NET will
/// complain. Set the type for the section handler to , for example:
- ///
+ ///
///
///
- ///
+ /// ]]>
///
///
/// The following example configures log4net using a configuration file, of which the
/// location is stored in the application's configuration file :
///
- ///
+ ///
/// using log4net.Config;
/// using System.IO;
/// using System.Configuration;
@@ -361,13 +361,13 @@ public static ICollection Configure(ILoggerRepository repository, XmlElement ele
///
/// In the .config file, the path to the log4net can be specified like this :
///
- ///
+ ///
///
///
///
///
- ///
+ /// ]]>
///
public static ICollection Configure(ILoggerRepository repository, FileInfo configFile)
{
diff --git a/src/log4net/Core/LogImpl.cs b/src/log4net/Core/LogImpl.cs
index 7ea86025e..2ac5b7d4f 100644
--- a/src/log4net/Core/LogImpl.cs
+++ b/src/log4net/Core/LogImpl.cs
@@ -1153,7 +1153,7 @@ public virtual void FatalFormat(IFormatProvider? provider, string format, params
///
/// For some log Logger object, when you write:
///
- ///
+ ///
/// log.Debug("This is entry number: " + i );
///
///
@@ -1163,7 +1163,7 @@ public virtual void FatalFormat(IFormatProvider? provider, string format, params
///
/// If you are worried about speed, then you should write:
///
- ///
+ ///
/// if (log.IsDebugEnabled())
/// {
/// log.Debug("This is entry number: " + i );
diff --git a/src/log4net/GlobalContext.cs b/src/log4net/GlobalContext.cs
index a41eb049e..c46c0a60c 100644
--- a/src/log4net/GlobalContext.cs
+++ b/src/log4net/GlobalContext.cs
@@ -40,7 +40,7 @@ namespace log4net;
///
///
///
-///
+///
/// GlobalContext.Properties["hostname"] = Environment.MachineName;
///
///
diff --git a/src/log4net/ILog.cs b/src/log4net/ILog.cs
index 56c350bc0..fdcf14fcd 100644
--- a/src/log4net/ILog.cs
+++ b/src/log4net/ILog.cs
@@ -46,8 +46,9 @@ namespace log4net;
/// do things differently.
///
///
-/// Simple example of logging messages
-///
+///
+/// Simple example of logging messages
+///
/// ILog log = LogManager.GetLogger("application-log");
///
/// log.Info("Application Start");
@@ -842,7 +843,7 @@ public interface ILog : ILoggerWrapper
/// disabled log debug statements.
///
/// For some ILog interface log , when you write:
- ///
+ ///
/// log.Debug("This is entry number: " + i );
///
///
@@ -852,7 +853,7 @@ public interface ILog : ILoggerWrapper
///
/// If you are worried about speed (who isn't), then you should write:
///
- ///
+ ///
/// if (log.IsDebugEnabled)
/// {
/// log.Debug("This is entry number: " + i );
@@ -871,13 +872,13 @@ public interface ILog : ILoggerWrapper
/// Alternatively if your logger is available statically then the is debug
/// enabled state can be stored in a static variable like this:
///
- ///
+ ///
/// private static readonly bool isDebugEnabled = log.IsDebugEnabled;
///
///
/// Then when you come to log you can write:
///
- ///
+ ///
/// if (isDebugEnabled)
/// {
/// log.Debug("This is entry number: " + i );
diff --git a/src/log4net/Layout/ILayout.cs b/src/log4net/Layout/ILayout.cs
index c59b00807..982cf9cda 100644
--- a/src/log4net/Layout/ILayout.cs
+++ b/src/log4net/Layout/ILayout.cs
@@ -56,7 +56,7 @@ public interface ILayout
/// event to be formatted as a then the following
/// code can be used to format the event into a .
///
- ///
+ ///
/// StringWriter writer = new StringWriter();
/// Layout.Format(writer, loggingEvent);
/// string formattedEvent = writer.ToString();
diff --git a/src/log4net/Layout/PatternLayout.cs b/src/log4net/Layout/PatternLayout.cs
index 1fee419a3..03559f161 100644
--- a/src/log4net/Layout/PatternLayout.cs
+++ b/src/log4net/Layout/PatternLayout.cs
@@ -67,7 +67,7 @@ namespace log4net.Layout;
/// that the log4net environment was set to use a PatternLayout. Then the
/// statements
///
-///
+///
/// ILog log = LogManager.GetLogger(typeof(TestApp));
/// log.Debug("Message 1");
/// log.Warn("Message 2");
diff --git a/src/log4net/Layout/XmlLayout.cs b/src/log4net/Layout/XmlLayout.cs
index 90b59c1ce..fcacec52f 100644
--- a/src/log4net/Layout/XmlLayout.cs
+++ b/src/log4net/Layout/XmlLayout.cs
@@ -43,7 +43,7 @@ namespace log4net.Layout;
/// the output goes, then a well-formed XML file would
/// be:
///
-///
+///
/// <?xml version="1.0" ?>
///
/// <!DOCTYPE log4net:events SYSTEM "log4net-events.dtd" [<!ENTITY data SYSTEM "abc">]>
diff --git a/src/log4net/LogManager.cs b/src/log4net/LogManager.cs
index 36e958b86..cb4d0664d 100644
--- a/src/log4net/LogManager.cs
+++ b/src/log4net/LogManager.cs
@@ -40,8 +40,9 @@ namespace log4net;
/// See the interface for more details.
///
///
-/// Simple example of logging messages
-///
+///
+/// Simple example of logging messages
+///
/// ILog log = LogManager.GetLogger("application-log");
///
/// log.Info("Application Start");
diff --git a/src/log4net/LogicalThreadContext.cs b/src/log4net/LogicalThreadContext.cs
index 13e7e1def..dc0a55a8e 100644
--- a/src/log4net/LogicalThreadContext.cs
+++ b/src/log4net/LogicalThreadContext.cs
@@ -63,19 +63,20 @@ namespace log4net;
/// It will not store any property values set on it.
///
///
-/// Example of using the thread context properties to store a username.
-///
+///
+/// Example of using the thread context properties to store a username.
+///
/// LogicalThreadContext.Properties["user"] = userName;
-/// log.Info("This log message has a LogicalThreadContext Property called 'user'");
+/// log.Info("This log message has a LogicalThreadContext Property called 'user'");
///
///
-/// Example of how to push a message into the context stack
-///
-/// using(LogicalThreadContext.Stacks["LDC"].Push("my context message"))
-/// {
-/// log.Info("This log message has a LogicalThreadContext Stack message that includes 'my context message'");
-///
-/// } // at the end of the using block the message is automatically popped
+///
+/// Example of how to push a message into the context stack
+///
+/// using (LogicalThreadContext.Stacks["LDC"].Push("my context message"))
+/// {
+/// log.Info("This log message has a LogicalThreadContext Stack message that includes 'my context message'");
+/// } // at the end of the using block the message is automatically popped
///
///
///
diff --git a/src/log4net/NDC.cs b/src/log4net/NDC.cs
index 57d3eb378..12f9b703b 100644
--- a/src/log4net/NDC.cs
+++ b/src/log4net/NDC.cs
@@ -49,13 +49,13 @@ namespace log4net;
/// calling thread.
///
///
-/// How to push a message into the context
-///
-/// using (NDC.Push("my context message"))
-/// {
-/// ... all log calls will have 'my context message' included ...
-///
-/// } // at the end of the using block the message is automatically removed
+///
+/// How to push a message into the context
+///
+/// using (NDC.Push("my context message"))
+/// {
+/// ... all log calls will have 'my context message' included ...
+/// } // at the end of the using block the message is automatically removed
///
///
///
@@ -193,12 +193,13 @@ public static class NDC
/// context.
///
///
- /// Simple example of using the method with the keyword.
- ///
- /// using(log4net.NDC.Push("NDC_Message"))
+ ///
+ /// Simple example of using the method with the keyword.
+ ///
+ /// using (log4net.NDC.Push("NDC_Message"))
/// {
- /// log.Warn("This should have an NDC message");
- /// }
+ /// log.Warn("This should have an NDC message");
+ /// }
///
///
/*[Obsolete("NDC has been replaced by ThreadContext.Stacks")]*/
@@ -227,13 +228,14 @@ public static class NDC
/// context.
///
///
- /// Simple example of using the method with the keyword.
- ///
- /// var someValue = "ExampleContext"
- /// using(log4net.NDC.PushFormat("NDC_Message {0}", someValue))
+ ///
+ /// Simple example of using the method with the keyword.
+ ///
+ /// var someValue = "ExampleContext";
+ /// using (log4net.NDC.PushFormat("NDC_Message {0}", someValue))
/// {
- /// log.Warn("This should have an NDC message");
- /// }
+ /// log.Warn("This should have an NDC message");
+ /// }
///
///
/*[Obsolete("NDC has been replaced by ThreadContext.Stacks")]*/
diff --git a/src/log4net/ThreadContext.cs b/src/log4net/ThreadContext.cs
index 4267efa10..e4ca2d3de 100644
--- a/src/log4net/ThreadContext.cs
+++ b/src/log4net/ThreadContext.cs
@@ -47,19 +47,20 @@ namespace log4net;
/// The Thread Context is managed on a per thread basis.
///
///
-/// Example of using the thread context properties to store a username.
-///
+///
+/// Example of using the thread context properties to store a username.
+///
/// ThreadContext.Properties["user"] = userName;
-/// log.Info("This log message has a ThreadContext Property called 'user'");
+/// log.Info("This log message has a ThreadContext Property called 'user'");
///
///
-/// Example of how to push a message into the context stack
-///
-/// using(ThreadContext.Stacks["NDC"].Push("my context message"))
-/// {
-/// log.Info("This log message has a ThreadContext Stack message that includes 'my context message'");
-///
-/// } // at the end of the using block the message is automatically popped
+///
+/// Example of how to push a message into the context stack
+///
+/// using (ThreadContext.Stacks["NDC"].Push("my context message"))
+/// {
+/// log.Info("This log message has a ThreadContext Stack message that includes 'my context message'");
+/// } // at the end of the using block the message is automatically popped
///
///
///
diff --git a/src/log4net/Util/ILogExtensions.cs b/src/log4net/Util/ILogExtensions.cs
index 1b216abbf..0dbe98ab9 100644
--- a/src/log4net/Util/ILogExtensions.cs
+++ b/src/log4net/Util/ILogExtensions.cs
@@ -34,8 +34,9 @@ namespace log4net.Util;
/// configuration.
///
///
-/// Simple example of logging messages
-///
+///
+/// Simple example of logging messages
+///
/// using log4net.Util;
///
/// ILog log = LogManager.GetLogger("application-log");
diff --git a/src/log4net/Util/LogLog.cs b/src/log4net/Util/LogLog.cs
index 05dacb5cf..b4b2eb860 100644
--- a/src/log4net/Util/LogLog.cs
+++ b/src/log4net/Util/LogLog.cs
@@ -176,13 +176,13 @@ static LogLog()
/// The following example enables internal debugging using the
/// application configuration file :
///
- ///
+ ///
///
///
///
///
- ///
+ /// ]]>
///
public static bool InternalDebugging { get; set; }
@@ -212,13 +212,13 @@ static LogLog()
///
/// The following example disables internal logging using the
/// application configuration file :
- ///
+ ///
///
///
///
///
- ///
+ /// ]]>
///
public static bool QuietMode { get; set; }
diff --git a/src/log4net/Util/LogicalThreadContextStack.cs b/src/log4net/Util/LogicalThreadContextStack.cs
index 8de4a5bef..6339525ee 100644
--- a/src/log4net/Util/LogicalThreadContextStack.cs
+++ b/src/log4net/Util/LogicalThreadContextStack.cs
@@ -89,7 +89,7 @@ internal LogicalThreadContextStack(string propertyKey, LogicalThreadContextStack
/// You do not need to use this method if you always guarantee to call
/// the method of the
/// returned from even in exceptional circumstances,
- /// for example by using the using(log4net.LogicalThreadContext.Stacks["NDC"].Push("Stack_Message"))
+ /// for example by using the using (log4net.LogicalThreadContext.Stacks["NDC"].Push("Stack_Message"))
/// syntax.
///
///
@@ -135,12 +135,13 @@ internal LogicalThreadContextStack(string propertyKey, LogicalThreadContextStack
/// context.
///
///
- /// Simple example of using the method with the keyword.
- ///
- /// using(log4net.LogicalThreadContext.Stacks["NDC"].Push("Stack_Message"))
+ ///
+ /// Simple example of using the method with the keyword.
+ ///
+ /// using (log4net.LogicalThreadContext.Stacks["NDC"].Push("Stack_Message"))
/// {
- /// log.Warn("This should have an ThreadContext Stack message");
- /// }
+ /// log.Warn("This should have an ThreadContext Stack message");
+ /// }
///
///
public IDisposable Push(string? message)
diff --git a/src/log4net/Util/OptionConverter.cs b/src/log4net/Util/OptionConverter.cs
index 141362eff..07cae51dc 100644
--- a/src/log4net/Util/OptionConverter.cs
+++ b/src/log4net/Util/OptionConverter.cs
@@ -303,7 +303,7 @@ public static object ConvertTypeTo(object sourceInstance, Type targetType)
/// For example, if props contains key=value , then the call
///
///
- ///
+ ///
/// string s = OptionConverter.SubstituteVariables("Value of key is ${key}.");
///
///
@@ -319,7 +319,7 @@ public static object ConvertTypeTo(object sourceInstance, Type targetType)
/// "nonExistentKey", then the call
///
///
- ///
+ ///
/// string s = OptionConverter.SubstituteVariables("Value of nonExistentKey is [${nonExistentKey}]");
///
///
diff --git a/src/log4net/Util/ThreadContextStack.cs b/src/log4net/Util/ThreadContextStack.cs
index 6b7a9375c..e187e166f 100644
--- a/src/log4net/Util/ThreadContextStack.cs
+++ b/src/log4net/Util/ThreadContextStack.cs
@@ -79,7 +79,7 @@ internal ThreadContextStack()
/// You do not need to use this method if you always guarantee to call
/// the method of the
/// returned from even in exceptional circumstances,
- /// for example by using the using(log4net.ThreadContext.Stacks["NDC"].Push("Stack_Message"))
+ /// for example by using the using (log4net.ThreadContext.Stacks["NDC"].Push("Stack_Message"))
/// syntax.
///
///
@@ -121,12 +121,13 @@ internal ThreadContextStack()
/// context.
///
///
- /// Simple example of using the method with the keyword.
- ///
- /// using(log4net.ThreadContext.Stacks["NDC"].Push("Stack_Message"))
+ ///
+ /// Simple example of using the method with the keyword.
+ ///
+ /// using (log4net.ThreadContext.Stacks["NDC"].Push("Stack_Message"))
/// {
- /// log.Warn("This should have an ThreadContext Stack message");
- /// }
+ /// log.Warn("This should have an ThreadContext Stack message");
+ /// }
///
///
public IDisposable Push(string? message)
diff --git a/src/site/antora/modules/ROOT/pages/development.adoc b/src/site/antora/modules/ROOT/pages/development.adoc
index 67aea4bd2..bac72774a 100644
--- a/src/site/antora/modules/ROOT/pages/development.adoc
+++ b/src/site/antora/modules/ROOT/pages/development.adoc
@@ -76,6 +76,14 @@ You can build and view the website as follows:
You can view the generated website with a browser by pointing it to `target/site` directory.
+The link:api/index.html[API reference] is generated next to it, under `target/site/api`, from the
+XML documentation comments:
+
+[source,bash/powershell]
+----
+./scripts/build-api-docs.ps1
+----
+
[#release-instructions]
== Release instructions
diff --git a/src/site/antora/modules/ROOT/pages/manual/index.adoc b/src/site/antora/modules/ROOT/pages/manual/index.adoc
index 4163c7d97..1b3b51a97 100644
--- a/src/site/antora/modules/ROOT/pages/manual/index.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/index.adoc
@@ -39,6 +39,7 @@ log4net runs on net462 or higher and any framework supporting netstandard2.0.
== Is there example code for using log4net?
You can find many examples in https://github.com/apache/logging-log4net/tree/master/examples[log4net/examples].
+The link:../api/index.html[API reference] documents every public type and member.
== What does log4net offer?
diff --git a/src/site/docfx/docfx.json b/src/site/docfx/docfx.json
new file mode 100644
index 000000000..129d68d6b
--- /dev/null
+++ b/src/site/docfx/docfx.json
@@ -0,0 +1,73 @@
+{
+ "metadata": [
+ {
+ "src": [
+ {
+ "src": "../..",
+ "files": [
+ "log4net/log4net.csproj"
+ ]
+ }
+ ],
+ "dest": "log4net",
+ "properties": {
+ "TargetFramework": "net462"
+ },
+ "namespaceLayout": "nested",
+ "memberLayout": "samePage",
+ "disableGitFeatures": true
+ },
+ {
+ "src": [
+ {
+ "src": "../..",
+ "files": [
+ "log4net.Ext.Mail/log4net.Ext.Mail.csproj"
+ ]
+ }
+ ],
+ "dest": "log4net.Ext.Mail",
+ "properties": {
+ "TargetFramework": "netstandard2.0",
+ "ProduceReferenceAssembly": "true"
+ },
+ "namespaceLayout": "nested",
+ "memberLayout": "samePage",
+ "disableGitFeatures": true
+ }
+ ],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "index.md",
+ "manual.md",
+ "toc.yml",
+ "log4net/**/*.yml",
+ "log4net.Ext.Mail/**/*.yml"
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "files": [
+ "logo.svg"
+ ]
+ }
+ ],
+ "dest": "../../../target/site/api",
+ "template": [
+ "default",
+ "modern",
+ "template"
+ ],
+ "globalMetadata": {
+ "_appTitle": "Apache log4net API Reference",
+ "_appName": "Apache log4net",
+ "_appLogoPath": "logo.svg",
+ "_appFaviconPath": "logo.svg",
+ "_enableSearch": true,
+ "_disableContribution": true
+ }
+ }
+}
diff --git a/src/site/docfx/index.md b/src/site/docfx/index.md
new file mode 100644
index 000000000..c8037f14f
--- /dev/null
+++ b/src/site/docfx/index.md
@@ -0,0 +1,7 @@
+# API Reference
+
+This reference is generated from the XML documentation comments of the two shipping assemblies.
+Start with the [log4net](xref:log4net) namespace, or with
+[log4net.Ext.Mail.Appender](xref:log4net.Ext.Mail.Appender) for the MailKit based SMTP appender.
+
+Configuration, examples and the FAQ are in the [manual](manual.md).
diff --git a/src/site/docfx/logo.svg b/src/site/docfx/logo.svg
new file mode 100644
index 000000000..10fbd7c26
--- /dev/null
+++ b/src/site/docfx/logo.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/src/site/docfx/manual.md b/src/site/docfx/manual.md
new file mode 100644
index 000000000..cd1b32ad5
--- /dev/null
+++ b/src/site/docfx/manual.md
@@ -0,0 +1,3 @@
+---
+redirect_url: ../index.html
+---
diff --git a/src/site/docfx/template/public/main.css b/src/site/docfx/template/public/main.css
new file mode 100644
index 000000000..45c6bbce7
--- /dev/null
+++ b/src/site/docfx/template/public/main.css
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/* The modern template leaves the logo at its intrinsic size, 360 by 140 for log4net.svg, and
+ the brand is a flex row, which collapses the whitespace between the logo and the title. */
+#logo {
+ height: 2.5rem;
+ width: auto;
+ margin-right: 0.75rem;
+}
diff --git a/src/site/docfx/toc.yml b/src/site/docfx/toc.yml
new file mode 100644
index 000000000..787c5c3fe
--- /dev/null
+++ b/src/site/docfx/toc.yml
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+- name: log4net
+ href: log4net/
+- name: log4net.Ext.Mail
+ href: log4net.Ext.Mail/
+- name: Manual
+ href: manual.md