From c9e278b2c29944c9d2eadd310e66a8016d21eebf Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:24:15 +0200 Subject: [PATCH 1/9] Target .NET 10 / C# 14 for tests The sources use C# 14, so a .NET 10 SDK is now required. Test and integration-test projects move from net8.0 to net10.0; the shipped log4net assembly keeps net462;netstandard2.0. --- .github/workflows/build.yaml | 2 +- src/changelog/3.4.0/305-target-dotnet-10.xml | 13 +++++++++++++ .../log4net-611-main/log4net-611-main.csproj | 2 +- .../log4net-672/log4net-672.csproj | 2 +- .../log4net-673/log4net-673.csproj | 2 +- .../log4net-681/log4net-681.csproj | 2 +- .../log4net.Ext.Mail.Tests.csproj | 2 +- src/log4net.Tests/log4net.Tests.csproj | 9 ++++++--- 8 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 src/changelog/3.4.0/305-target-dotnet-10.xml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 86205a469..f25255ccf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -47,7 +47,7 @@ jobs: - name: Set up dotnet uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # 4.0.1 with: - dotnet-version: 8 + dotnet-version: 10 dotnet-quality: ga - name: Build diff --git a/src/changelog/3.4.0/305-target-dotnet-10.xml b/src/changelog/3.4.0/305-target-dotnet-10.xml new file mode 100644 index 000000000..e2ca2d752 --- /dev/null +++ b/src/changelog/3.4.0/305-target-dotnet-10.xml @@ -0,0 +1,13 @@ + + + + + build and test with .NET 10: the test and integration-test projects now target `net10.0` + instead of `net8.0`, and a .NET 10 SDK is required because the sources use C# 14 language + features. The published `log4net` assembly still targets `net462` and `netstandard2.0`, so + nothing changes for consumers + + diff --git a/src/integration-testing/log4net-611-main/log4net-611-main.csproj b/src/integration-testing/log4net-611-main/log4net-611-main.csproj index 644a52268..ce5ad80f6 100644 --- a/src/integration-testing/log4net-611-main/log4net-611-main.csproj +++ b/src/integration-testing/log4net-611-main/log4net-611-main.csproj @@ -2,7 +2,7 @@ Exe - net462;net8.0 + net462;net10.0 log4net_611_main enable enable diff --git a/src/integration-testing/log4net-672/log4net-672.csproj b/src/integration-testing/log4net-672/log4net-672.csproj index 7905ac114..33f8839b0 100644 --- a/src/integration-testing/log4net-672/log4net-672.csproj +++ b/src/integration-testing/log4net-672/log4net-672.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 log4net_672 disable enable diff --git a/src/integration-testing/log4net-673/log4net-673.csproj b/src/integration-testing/log4net-673/log4net-673.csproj index 8521944b5..ed237a8ce 100644 --- a/src/integration-testing/log4net-673/log4net-673.csproj +++ b/src/integration-testing/log4net-673/log4net-673.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 log4net_673 disable disable diff --git a/src/integration-testing/log4net-681/log4net-681.csproj b/src/integration-testing/log4net-681/log4net-681.csproj index 238e930b1..8cdaa70ed 100644 --- a/src/integration-testing/log4net-681/log4net-681.csproj +++ b/src/integration-testing/log4net-681/log4net-681.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 log4net_681 enable enable diff --git a/src/log4net.Ext.Mail.Tests/log4net.Ext.Mail.Tests.csproj b/src/log4net.Ext.Mail.Tests/log4net.Ext.Mail.Tests.csproj index 0add8141d..6507ad71b 100644 --- a/src/log4net.Ext.Mail.Tests/log4net.Ext.Mail.Tests.csproj +++ b/src/log4net.Ext.Mail.Tests/log4net.Ext.Mail.Tests.csproj @@ -1,7 +1,7 @@  true - net8.0 + net10.0 NETSDK1138;CS1701 Library bin\$(Configuration) diff --git a/src/log4net.Tests/log4net.Tests.csproj b/src/log4net.Tests/log4net.Tests.csproj index 368fd27e9..1ce53b952 100644 --- a/src/log4net.Tests/log4net.Tests.csproj +++ b/src/log4net.Tests/log4net.Tests.csproj @@ -1,7 +1,11 @@  true - net462;net8.0 + net462;net10.0 + + net10.0 NETSDK1138;CS1701 Library bin\$(Configuration) @@ -31,7 +35,7 @@ - + @@ -39,5 +43,4 @@ Always - \ No newline at end of file From 7f63f49035e94b2d93eb28c1ad4fe9f99f412db1 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:26:06 +0200 Subject: [PATCH 2/9] Build net4x without Mono Remove MonoForFramework.targets and its FrameworkPathOverride handling. The net462/net472 targets compile against the Microsoft.NETFramework.ReferenceAssemblies packages that the .NET SDK already references implicitly, so Mono is only needed to run .NET Framework assemblies - not to build them (and mono is now deprecated by Microsoft). --- src/MonoForFramework.targets | 20 ------------------- .../3.4.0/305-build-net4x-without-mono.xml | 14 +++++++++++++ src/log4net/log4net.csproj | 1 - 3 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 src/MonoForFramework.targets create mode 100644 src/changelog/3.4.0/305-build-net4x-without-mono.xml diff --git a/src/MonoForFramework.targets b/src/MonoForFramework.targets deleted file mode 100644 index 81b01e2b6..000000000 --- a/src/MonoForFramework.targets +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - /Library/Frameworks/Mono.framework/Versions/Current/lib/mono - /usr/lib/mono - /usr/local/lib/mono - - $(BaseFrameworkPathOverrideForMono)/4.6.2-api - $(BaseFrameworkPathOverrideForMono)/4.7-api - $(BaseFrameworkPathOverrideForMono)/4.7.1-api - $(BaseFrameworkPathOverrideForMono)/4.7.2-api - $(BaseFrameworkPathOverrideForMono)/4.8-api - true - - $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) - - - diff --git a/src/changelog/3.4.0/305-build-net4x-without-mono.xml b/src/changelog/3.4.0/305-build-net4x-without-mono.xml new file mode 100644 index 000000000..260bb2c6c --- /dev/null +++ b/src/changelog/3.4.0/305-build-net4x-without-mono.xml @@ -0,0 +1,14 @@ + + + + + Mono is no longer required to build the `net462` and `net472` targets on Linux and macOS. + `MonoForFramework.targets` and its `FrameworkPathOverride` handling were removed in favour of + the `Microsoft.NETFramework.ReferenceAssemblies` packages that the .NET SDK already references + implicitly. Mono would only be needed to run .NET Framework assemblies, so the `net462` test + target is now skipped off Windows, where VSTest cannot host it + + diff --git a/src/log4net/log4net.csproj b/src/log4net/log4net.csproj index 3f78b5ff3..774b00aa6 100644 --- a/src/log4net/log4net.csproj +++ b/src/log4net/log4net.csproj @@ -101,7 +101,6 @@ log4net is designed with two distinct goals in mind: speed and flexibility - From 27216417ce4fe9410b64d6af14cf676ce71dace1 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:33:28 +0200 Subject: [PATCH 3/9] Build the container image from the .NET 10 SDK image Replace ubuntu:20.04 + dotnet-install.sh with mcr.microsoft.com/dotnet/sdk:10.0-noble - Ubuntu 24.04 with the SDK preinstalled. Drops the Mono packages, the apt-key/stable-focal repo, wget, tree and the DOTNET_ROOT/PATH handling, leaving no apt layer at all. Ubuntu 20.04 left standard support in April 2025. --- Dockerfile | 33 ++++++-------------- src/changelog/3.4.0/305-docker-sdk-image.xml | 13 ++++++++ 2 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 src/changelog/3.4.0/305-docker-sdk-image.xml diff --git a/Dockerfile b/Dockerfile index f39c387b2..5e7060103 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,34 +16,19 @@ # MAINTAINER Jan Friedrich -FROM ubuntu:20.04 -ENV DEBIAN_FRONTEND=noninteractive \ - TZ=Etc/UTC +# Ubuntu 24.04 (noble) with the .NET 10 SDK already installed - noble is the only Ubuntu +# variant published for .NET 10. The reference is fully qualified, so it needs no registry +# configuration and no login, unlike the short name it replaces. +FROM mcr.microsoft.com/dotnet/sdk:10.0-noble +ENV TZ=Etc/UTC -# Install Mono SDK (compiler, msbuild, runtime, etc.) -RUN apt-get update && \ - apt-get install -y gnupg ca-certificates && \ - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \ - echo "deb https://download.mono-project.com/repo/ubuntu stable-focal main" | tee /etc/apt/sources.list.d/mono-official-stable.list && \ - apt-get update && \ - apt-get install -y mono-complete && \ - rm -rf /var/lib/apt/lists/* +# Mono is not required: the net4x targets are compiled against the +# Microsoft.NETFramework.ReferenceAssemblies packages that the .NET SDK references implicitly. +# Mono would only be needed to *execute* net4x assemblies, which this image does not do. -# Check Mono version -RUN mono --version - -RUN apt-get update \ - && apt-get upgrade -y \ - && apt-get install -y wget \ - && apt-get install -y tree \ - && wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ - && chmod +x ./dotnet-install.sh \ - && ./dotnet-install.sh --channel 8.0 ENV DOTNET_NOLOGO=true ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true -ENV DOTNET_ROOT=/root/.dotnet -ENV PATH="$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools" - + ADD . /logging-log4net RUN dotnet restore /logging-log4net/src/log4net.sln RUN dotnet build -c Release /logging-log4net/src/log4net.sln diff --git a/src/changelog/3.4.0/305-docker-sdk-image.xml b/src/changelog/3.4.0/305-docker-sdk-image.xml new file mode 100644 index 000000000..dc06e11f2 --- /dev/null +++ b/src/changelog/3.4.0/305-docker-sdk-image.xml @@ -0,0 +1,13 @@ + + + + + build the container image from `mcr.microsoft.com/dotnet/sdk:10.0-noble` instead of installing + a SDK into `ubuntu:20.04` with `dotnet-install.sh`. This moves off an Ubuntu release that left + standard support in April 2025 and drops the download step, the Mono packages and the + `DOTNET_ROOT` and `PATH` handling + + From 96123c999540ccaed5c089db7738b549a6ccda24 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:34:49 +0200 Subject: [PATCH 4/9] Stop the release scripts on failed steps $ErrorActionPreference does not apply to native commands, so a failing dotnet, git, zip, gpg or mvnw was ignored and the artifacts were packaged, signed and tagged anyway. Both scripts now set $PSNativeCommandUseErrorActionPreference (PowerShell 7.3+). Also fix build-preview.ps1 outside Windows, where the artifact paths handed to gpg used backslashes. --- scripts/build-preview.ps1 | 12 ++++++++++-- scripts/build-release.ps1 | 4 ++++ src/changelog/3.4.0/305-fix-release-scripts.xml | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/changelog/3.4.0/305-fix-release-scripts.xml diff --git a/scripts/build-preview.ps1 b/scripts/build-preview.ps1 index e1fcfbaba..a7f0badb8 100644 --- a/scripts/build-preview.ps1 +++ b/scripts/build-preview.ps1 @@ -2,11 +2,19 @@ param( $Version = '3.4.0', $Preview = '1' ) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +# $ErrorActionPreference alone does not apply to native commands: dotnet, gpg and git only set +# $LASTEXITCODE, so without this a failing build would still be signed and tagged. +# Requires PowerShell 7.3+. +$PSNativeCommandUseErrorActionPreference = $true + 'building ...' dotnet build -c Release "-p:GeneratePackages=true;PackageVersion=$Version-preview.$Preview" $PSScriptRoot/../src/log4net.sln 'signing ...' -gpg --armor --output $PSScriptRoot\..\build\artifacts\log4net.$Version-preview.$Preview.nupkg.asc --detach-sig $PSScriptRoot\..\build\artifacts\log4net.$Version-preview.$Preview.nupkg -gpg --armor --output $PSScriptRoot\..\build\artifacts\log4net.Ext.Mail.$Version-preview.$Preview.nupkg.asc --detach-sig $PSScriptRoot\..\build\artifacts\log4net.Ext.Mail.$Version-preview.$Preview.nupkg +gpg --armor --output $PSScriptRoot/../build/artifacts/log4net.$Version-preview.$Preview.nupkg.asc --detach-sig $PSScriptRoot/../build/artifacts/log4net.$Version-preview.$Preview.nupkg +gpg --armor --output $PSScriptRoot/../build/artifacts/log4net.Ext.Mail.$Version-preview.$Preview.nupkg.asc --detach-sig $PSScriptRoot/../build/artifacts/log4net.Ext.Mail.$Version-preview.$Preview.nupkg 'create tag?' pause 'creating tag ...' diff --git a/scripts/build-release.ps1 b/scripts/build-release.ps1 index 271129d09..33fc41723 100644 --- a/scripts/build-release.ps1 +++ b/scripts/build-release.ps1 @@ -4,6 +4,10 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +# $ErrorActionPreference alone does not apply to native commands: dotnet, git, zip, gpg and mvnw +# only set $LASTEXITCODE, so without this a failing build would still be packaged and signed. +# Requires PowerShell 7.3+. +$PSNativeCommandUseErrorActionPreference = $true function Write-HashAndSignature { diff --git a/src/changelog/3.4.0/305-fix-release-scripts.xml b/src/changelog/3.4.0/305-fix-release-scripts.xml new file mode 100644 index 000000000..657efaadc --- /dev/null +++ b/src/changelog/3.4.0/305-fix-release-scripts.xml @@ -0,0 +1,15 @@ + + + + + fix the release scripts continuing after a failed step: `$ErrorActionPreference` does not apply + to native commands, so a failing `dotnet`, `git`, `zip`, `gpg` or `mvnw` went unnoticed and the + artifacts were packaged, signed and tagged anyway. Both `scripts/build-preview.ps1` and + `scripts/build-release.ps1` now set `$PSNativeCommandUseErrorActionPreference`. Also fix + `scripts/build-preview.ps1` outside Windows, where the artifact paths handed to `gpg` were + built with backslashes, which are not path separators on Linux or macOS + + From 9fab1adbd72b50f2acb0a9b1569316c475611586 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:35:27 +0200 Subject: [PATCH 5/9] Fix the log4net.Ext.Mail package readme PackageReadmeFile requires the readme at the package root, so packing README.md without a PackagePath failed with NU5039. Matches the pattern already used in log4net.csproj. --- .../3.4.0/305-fix-ext-mail-package-readme.xml | 12 ++++++++++++ src/log4net.Ext.Mail/log4net.Ext.Mail.csproj | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/changelog/3.4.0/305-fix-ext-mail-package-readme.xml diff --git a/src/changelog/3.4.0/305-fix-ext-mail-package-readme.xml b/src/changelog/3.4.0/305-fix-ext-mail-package-readme.xml new file mode 100644 index 000000000..3aa37d0ac --- /dev/null +++ b/src/changelog/3.4.0/305-fix-ext-mail-package-readme.xml @@ -0,0 +1,12 @@ + + + + + fix the `log4net.Ext.Mail` package failing to pack with `NU5039` (`The readme file 'README.md' + does not exist in the package`), because the `README.md` item was packed without a + `PackagePath` + + diff --git a/src/log4net.Ext.Mail/log4net.Ext.Mail.csproj b/src/log4net.Ext.Mail/log4net.Ext.Mail.csproj index 6da32c038..2a1828760 100644 --- a/src/log4net.Ext.Mail/log4net.Ext.Mail.csproj +++ b/src/log4net.Ext.Mail/log4net.Ext.Mail.csproj @@ -70,7 +70,7 @@ - + From d398490149df57cdb50456afda81f5039ea1fb3c Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:37:41 +0200 Subject: [PATCH 6/9] Quieten the TelnetAppender test Dispose disposed the TcpClient while the reader was blocked in stream.Read, so teardown always aborted the socket and dumped an IOException with a stack trace, even on a passing run. Shut the socket down first, so the read returns 0 and the loop ends normally, and only report exceptions that arrive before disposal. Progress chatter removed; the test now prints nothing unless it fails. Two things this fixes beyond the noise: - The cancellation token never actually stopped the loop. The check ran only after a successful read, so the reader could only be broken by disposing the socket under it. Now Shutdown(SocketShutdown.Both) ends the stream and the loop exits through its normal condition. - Diagnostics are preserved for real failures. log is still wired to TestContext.Out.WriteLine, gated on !_disposing - so a genuine client error still surfaces, and Assert.Fail on timeout still reports what was received. --- .../Appender/Internal/SimpleTelnetClient.cs | 47 +++++++++++++------ .../Appender/TelnetAppenderTest.cs | 5 -- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/log4net.Tests/Appender/Internal/SimpleTelnetClient.cs b/src/log4net.Tests/Appender/Internal/SimpleTelnetClient.cs index 38f104ff7..bc13c8c9e 100644 --- a/src/log4net.Tests/Appender/Internal/SimpleTelnetClient.cs +++ b/src/log4net.Tests/Appender/Internal/SimpleTelnetClient.cs @@ -38,59 +38,78 @@ internal sealed class SimpleTelnetClient( { private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly TcpClient _client = new(); + private volatile bool _disposing; /// /// Runs the client (in a task) /// + /// Callback for unexpected errors - a passing run stays silent internal void Run(Action log) => Task.Run(() => { try { - log("client: starting ..."); _client.Connect(new IPEndPoint(IPAddress.Loopback, port)); - log("client: connected"); // Get a stream object for reading and writing using NetworkStream stream = _client.GetStream(); - log("client: has stream"); int i; byte[] bytes = new byte[256]; - // Loop to receive all the data sent by the server + // Loop to receive all the data sent by the server. Dispose shuts the socket down, + // which ends the stream, so this read returns 0 and the loop exits without throwing. while ((i = stream.Read(bytes, 0, bytes.Length)) != 0) { - string data = System.Text.Encoding.ASCII.GetString(bytes, 0, i); - log("client: read: " + data); - received(data); + received(System.Text.Encoding.ASCII.GetString(bytes, 0, i)); if (_cancellationTokenSource.Token.IsCancellationRequested) { - log("client: canceled"); return; } } - log("client: end of stream"); } // The test asserts on the received data, so a failing client must not end up - // as an unobserved task exception - log it instead. + // as an unobserved task exception - log it instead. Anything thrown once Dispose + // has started is teardown noise, so only genuine failures reach the output. catch (SocketException e) { - log("client: error: " + e); + Report(e); } catch (IOException e) { - log("client: error: " + e); + Report(e); } catch (ObjectDisposedException e) { - // expected when the client is disposed while reading - log("client: disposed: " + e.Message); + Report(e); + } + + void Report(Exception e) + { + if (!_disposing) + { + log("client: error: " + e); + } } }, _cancellationTokenSource.Token); /// public void Dispose() { + _disposing = true; _cancellationTokenSource.Cancel(); + // Shut the socket down before disposing it: that ends the stream cleanly, so a read + // blocked in Run returns 0 instead of failing with a connection abort. + try + { + _client.Client?.Shutdown(SocketShutdown.Both); + } + catch (SocketException) + { + // not connected - nothing to shut down + } + catch (ObjectDisposedException) + { + // already disposed - nothing to shut down + } _cancellationTokenSource.Dispose(); _client.Dispose(); } diff --git a/src/log4net.Tests/Appender/TelnetAppenderTest.cs b/src/log4net.Tests/Appender/TelnetAppenderTest.cs index 9596f065f..e8cf95ec5 100644 --- a/src/log4net.Tests/Appender/TelnetAppenderTest.cs +++ b/src/log4net.Tests/Appender/TelnetAppenderTest.cs @@ -86,15 +86,11 @@ public void TelnetTest() { using (SimpleTelnetClient telnetClient = new(Received, port)) { - TestContext.Out.WriteLine("test: starting client ..."); telnetClient.Run(TestContext.Out.WriteLine); WaitForReceived("welcome message", WelcomeMessage); ILogger logger = repository.GetLogger("Telnet"); - TestContext.Out.WriteLine("test: logging to client ..."); logger.Log(typeof(TelnetAppenderTest), Level.Info, logId, null); - TestContext.Out.WriteLine("test: waiting for message of client ..."); WaitForReceived("log message", logId); - TestContext.Out.WriteLine("test: canceling client ..."); } } finally @@ -130,7 +126,6 @@ void WaitForReceived(string what, string expected) } Thread.Sleep(20); } - TestContext.Out.WriteLine($"receiver: received {what} after {stopwatch.ElapsedMilliseconds} ms"); } } From a73070c590be08aa12f942878605e964d5f91b36 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 4 Aug 2026 23:43:44 +0200 Subject: [PATCH 7/9] Update BUILDING.md to match the actual build --- doc/BUILDING.md | 62 +++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/doc/BUILDING.md b/doc/BUILDING.md index acd888896..396803001 100644 --- a/doc/BUILDING.md +++ b/doc/BUILDING.md @@ -6,11 +6,10 @@ Log4net provides support for the following targets TL;DR (Windows): - install Visual Studio Build Tools (at least VS2019) -- install nodejs (at least v16) -- install dotnet (v8+) and the .NET SDK (current latest) +- install the .NET SDK (v10 or better - the sources use C# 14 language features) - in the project folder: - - `npm i` - - `npm run build` + - `dotnet build src/log4net.sln` + - `dotnet test src/log4net.sln` TL;DR (Docker): - install docker (if you haven't already) @@ -25,49 +24,42 @@ TL;DR (Docker): - `dotnet test /logging-log4net/src/log4net.sln` TL;DR (!Windows): -- install the dotnet SDK - v8 or better -- install Mono (you're going to need it to target certain versions of .NET) -- install nodejs 16+ - - in the project folder: - - `npm i` - - `export DOTNET_CORE=1 npm run build` - - we force using `dotnet` on non-windows targets for now. At some point, - this should become automatic +- install the dotnet SDK - v10 or better (the sources use C# 14 language features) +- in the project folder: + - `dotnet build src/log4net.sln` + - `dotnet test src/log4net.sln` +- Mono is *not* required. The `net462` / `net472` targets are compiled against the + `Microsoft.NETFramework.ReferenceAssemblies` packages that the .NET SDK references + implicitly. Mono would only be needed to *execute* net4x assemblies. +- the `log4net.Tests` project skips `net462` on non-Windows platforms: VSTest hosts net4x tests + through `TestHostNetFramework/testhost.exe`, a .NET Framework executable, so the run aborts + with `TESTRUNABORT` on Linux and macOS. The `net462` tests run only on the Windows job in CI. ## The full story Options: - build locally. Suggested environment: - Pre-requisites: - - Visual Studio 2019 Build Tools - - include desktop targets at least - - include dotnet core targets or download and install - the latest dotnet sdk (you will need at least v8) + - the .NET SDK, at least v10 (the sources use C# 14 language features) + - on Windows: Visual Studio 2019 Build Tools or later, including desktop targets + - only Windows can *run* the `net462` tests; elsewhere that target is skipped - Binaries can be built with a Visual Studio or Rider installation - Binaries, packages and a release zip can be built via commandline - - Ensure that you have a reasonably modern NodeJS installed (at least version 8+) - - `npm ci` - - `npm run build` - - optionally `npm test` to run all tests - - optionally `npm run release` to generate release artifacts -- build locally (CLI edition) - - install nodejs (at least v16) - - `npm i` - - `npm run build` -- build via docker for windows, using the `build-with-docker-for-windows.bat` script -- build via the vs2019 Windows AppVeyor image. There is an appveyor.yml file - included which (should) build if you set up AppVeyer to track - your fork. AppVeyer is free for open-source projects. - (TODO: should have a link to the official AppVeyor build) + - `dotnet build src/log4net.sln` + - optionally `dotnet test src/log4net.sln` to run all tests + - release artifacts are generated by the scripts under `scripts/`: + - `scripts/build-preview.ps1` for a preview package + - `scripts/build-release.ps1` for a full release +- build via docker, see the Docker section above ## Updating the site Log4Net uses Maven to build the site. Source artifacts can be found under `src/site`. -Building the site can be accomplished with `npm run build-site`. You should have maven -installed: -- Windows: get it from Scoop -- OSX: get it from Homebrew -- Linux: use your package manager +Building the site can be accomplished with `./mvnw site` (`.\mvnw.cmd site` on Windows) - the +committed Maven wrapper fetches the pinned Maven version, so only a JDK is required. This is +also what `scripts/build-release.ps1` uses. The `antora` profile inherited from `logging-parent` +activates automatically because `src/site/antora` exists; it installs Antora and runs it against +`antora-playbook.yaml`, so no separate npm step is needed. The site will be generated in `target/site`, and can be viewed locally. Updates should be pushed to the `asf-staging` branch of [https://github.com/apache/logging-log4net-site](https://github.com/apache/logging-log4net-site]) From cfeb2e5da662ac1f6914ac5729cd04bae07574be Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Wed, 5 Aug 2026 00:03:21 +0200 Subject: [PATCH 8/9] Run CI on the latest runner images (#305) macos-14 and ubuntu-22.04 are superseded by macos-latest (macOS 26) and ubuntu-latest (Ubuntu 24.04). Neither new image ships Mono, so net4x tests cannot run there - which is why log4net.Tests restricts net462 to Windows. Correct the reason given in BUILDING.md and the changelog. --- .github/workflows/build.yaml | 2 +- doc/BUILDING.md | 5 +++-- src/changelog/3.4.0/305-build-net4x-without-mono.xml | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f25255ccf..564337a35 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ macos-14, ubuntu-22.04, windows-latest ] + os: [ macos-latest, ubuntu-latest, windows-latest ] env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 diff --git a/doc/BUILDING.md b/doc/BUILDING.md index 396803001..083ef3579 100644 --- a/doc/BUILDING.md +++ b/doc/BUILDING.md @@ -32,8 +32,9 @@ TL;DR (!Windows): `Microsoft.NETFramework.ReferenceAssemblies` packages that the .NET SDK references implicitly. Mono would only be needed to *execute* net4x assemblies. - the `log4net.Tests` project skips `net462` on non-Windows platforms: VSTest hosts net4x tests - through `TestHostNetFramework/testhost.exe`, a .NET Framework executable, so the run aborts - with `TESTRUNABORT` on Linux and macOS. The `net462` tests run only on the Windows job in CI. + through `TestHostNetFramework/testhost.exe`, which needs a .NET Framework runtime - Mono, off + Windows. The Ubuntu 24.04 and macOS 26 runner images no longer ship Mono, so the run would + abort with `TESTRUNABORT`. The `net462` tests run only on the Windows job in CI. ## The full story diff --git a/src/changelog/3.4.0/305-build-net4x-without-mono.xml b/src/changelog/3.4.0/305-build-net4x-without-mono.xml index 260bb2c6c..cddb347e2 100644 --- a/src/changelog/3.4.0/305-build-net4x-without-mono.xml +++ b/src/changelog/3.4.0/305-build-net4x-without-mono.xml @@ -8,7 +8,8 @@ Mono is no longer required to build the `net462` and `net472` targets on Linux and macOS. `MonoForFramework.targets` and its `FrameworkPathOverride` handling were removed in favour of the `Microsoft.NETFramework.ReferenceAssemblies` packages that the .NET SDK already references - implicitly. Mono would only be needed to run .NET Framework assemblies, so the `net462` test - target is now skipped off Windows, where VSTest cannot host it + implicitly. Mono is still needed to *run* .NET Framework assemblies: CI moved to + `ubuntu-latest` (Ubuntu 24.04) and `macos-latest` (macOS 26), which no longer ship Mono, so + the `net462` tests now run only on the Windows job From 98742f239daec237c4fcbc35fe69c98b2abeedbe Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Wed, 5 Aug 2026 00:19:51 +0200 Subject: [PATCH 9/9] Remove the Mono guards from the tests (#305) Utils.IsMono and Utils.InconclusiveOnMono are gone, along with all 15 call sites. The net462 tests run only on Windows now and the net10.0 tests run on .NET 10, so the guard could never fire. SystemInfo.IsMono in the library is deliberately untouched: it detects the runtime executing log4net, which can still be Mono. --- .../3.4.0/305-build-net4x-without-mono.xml | 4 +++- .../Appender/RollingFileAppenderTest.cs | 10 ---------- .../Appender/SmtpPickupDirAppenderTest.cs | 3 --- src/log4net.Tests/Core/LoggingEventTest.cs | 2 -- src/log4net.Tests/Utils.cs | 19 ------------------- 5 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/changelog/3.4.0/305-build-net4x-without-mono.xml b/src/changelog/3.4.0/305-build-net4x-without-mono.xml index cddb347e2..70dd85155 100644 --- a/src/changelog/3.4.0/305-build-net4x-without-mono.xml +++ b/src/changelog/3.4.0/305-build-net4x-without-mono.xml @@ -10,6 +10,8 @@ the `Microsoft.NETFramework.ReferenceAssemblies` packages that the .NET SDK already references implicitly. Mono is still needed to *run* .NET Framework assemblies: CI moved to `ubuntu-latest` (Ubuntu 24.04) and `macos-latest` (macOS 26), which no longer ship Mono, so - the `net462` tests now run only on the Windows job + the `net462` tests now run only on the Windows job and the Mono guards in the test suite were + dropped. Support for *running* log4net under Mono is unaffected - the runtime check in + `log4net.Util.SystemInfo` is unchanged diff --git a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs index 83f9836ac..82c896d58 100644 --- a/src/log4net.Tests/Appender/RollingFileAppenderTest.cs +++ b/src/log4net.Tests/Appender/RollingFileAppenderTest.cs @@ -1279,7 +1279,6 @@ private static void AssertFileEquals(string filename, string contents) [Test] public void TestLogOutput() { - Utils.InconclusiveOnMono(); const string filename = "test_simple.log"; SilentErrorHandler sh = new(); ILogger log = CreateLogger(filename, new FileAppender.ExclusiveLock(), sh); @@ -1320,7 +1319,6 @@ public void TestExclusiveLockFails() [Test] public void TestExclusiveLockRecovers() { - Utils.InconclusiveOnMono(); const string filename = "test_exclusive_lock_recovers.log"; FileStream fs = new(filename, FileMode.Create, FileAccess.Write, FileShare.None); @@ -1344,7 +1342,6 @@ public void TestExclusiveLockRecovers() [Test] public void TestExclusiveLockLocks() { - Utils.InconclusiveOnMono(); const string filename = "test_exclusive_lock_locks.log"; bool locked = false; @@ -1381,7 +1378,6 @@ public void TestExclusiveLockLocks() [Test] public void TestMinimalLockFails() { - Utils.InconclusiveOnMono(); const string filename = "test_minimal_lock_fails.log"; FileStream fs = new(filename, FileMode.Create, FileAccess.Write, FileShare.None); @@ -1405,7 +1401,6 @@ public void TestMinimalLockFails() [Test] public void TestMinimalLockRecovers() { - Utils.InconclusiveOnMono(); const string filename = "test_minimal_lock_recovers.log"; FileStream fs = new(filename, FileMode.Create, FileAccess.Write, FileShare.None); @@ -1429,7 +1424,6 @@ public void TestMinimalLockRecovers() [Test] public void TestMinimalLockUnlocks() { - Utils.InconclusiveOnMono(); const string filename = "test_minimal_lock_unlocks.log"; SilentErrorHandler sh = new(); @@ -1455,7 +1449,6 @@ public void TestMinimalLockUnlocks() [Test] public void TestInterProcessLockFails() { - Utils.InconclusiveOnMono(); const string filename = "test_interprocess_lock_fails.log"; FileStream fs = new(filename, FileMode.Create, FileAccess.Write, FileShare.None); @@ -1479,7 +1472,6 @@ public void TestInterProcessLockFails() [Test] public void TestInterProcessLockRecovers() { - Utils.InconclusiveOnMono(); const string filename = "test_interprocess_lock_recovers.log"; FileStream fs = new(filename, FileMode.Create, FileAccess.Write, FileShare.None); @@ -1503,7 +1495,6 @@ public void TestInterProcessLockRecovers() [Test] public void TestInterProcessLockUnlocks() { - Utils.InconclusiveOnMono(); const string filename = "test_interprocess_lock_unlocks.log"; SilentErrorHandler sh = new(); @@ -1529,7 +1520,6 @@ public void TestInterProcessLockUnlocks() [Test] public void TestInterProcessLockRoll() { - Utils.InconclusiveOnMono(); const string filename = "test_interprocess_lock_roll.log"; SilentErrorHandler sh = new(); diff --git a/src/log4net.Tests/Appender/SmtpPickupDirAppenderTest.cs b/src/log4net.Tests/Appender/SmtpPickupDirAppenderTest.cs index 2817f6dc9..509b42a9b 100644 --- a/src/log4net.Tests/Appender/SmtpPickupDirAppenderTest.cs +++ b/src/log4net.Tests/Appender/SmtpPickupDirAppenderTest.cs @@ -159,7 +159,6 @@ private static void DestroyLogger() [Test] public void TestOutputContainsSentDate() { - Utils.InconclusiveOnMono(); SilentErrorHandler sh = new(); SmtpPickupDirAppender appender = CreateSmtpPickupDirAppender(sh); ILogger log = CreateLogger(appender); @@ -197,7 +196,6 @@ public void TestOutputContainsSentDate() Justification = "only .net8")] public void TestConfigurableFileExtension() { - Utils.InconclusiveOnMono(); const string fileExtension = "eml"; SilentErrorHandler sh = new(); SmtpPickupDirAppender appender = CreateSmtpPickupDirAppender(sh); @@ -221,7 +219,6 @@ public void TestConfigurableFileExtension() [Test] public void TestDefaultFileNameIsAGuid() { - Utils.InconclusiveOnMono(); SilentErrorHandler sh = new(); SmtpPickupDirAppender appender = CreateSmtpPickupDirAppender(sh); ILogger log = CreateLogger(appender); diff --git a/src/log4net.Tests/Core/LoggingEventTest.cs b/src/log4net.Tests/Core/LoggingEventTest.cs index 2add2d1a1..dfdb33aff 100644 --- a/src/log4net.Tests/Core/LoggingEventTest.cs +++ b/src/log4net.Tests/Core/LoggingEventTest.cs @@ -41,7 +41,6 @@ private static readonly DateTime _localTime [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2301:Do not use insecure deserializer BinaryFormatter")] public void SerializeDeserialize_BinaryFormatter() { - Utils.InconclusiveOnMono(); DateTime timestamp = _localTime.ToUniversalTime(); LoggingEvent ev = new(new() { @@ -92,7 +91,6 @@ public void SerializeDeserialize_BinaryFormatter() [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2301:Do not use insecure deserializer BinaryFormatter")] public void DeserializeV2() { - Utils.InconclusiveOnMono(); const string datPath = @"..\..\..\..\integration-testing\log4net2-SerializeEvent\SerializeV2Event.dat"; using Stream stream = File.OpenRead(datPath); BinaryFormatter formatter = new(); diff --git a/src/log4net.Tests/Utils.cs b/src/log4net.Tests/Utils.cs index dab7b4a25..477036973 100644 --- a/src/log4net.Tests/Utils.cs +++ b/src/log4net.Tests/Utils.cs @@ -17,9 +17,6 @@ // #endregion -using System; -using NUnit.Framework; - namespace log4net.Tests; /// @@ -27,22 +24,6 @@ namespace log4net.Tests; /// public static class Utils { - /// - /// Is the mono runtime used - /// - internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") is not null; - - /// - /// Skips the current test when run under mono - /// - internal static void InconclusiveOnMono() - { - if (IsMono) - { - Assert.Inconclusive("mono has a different behaviour"); - } - } - /// /// Sample property key ///