From 136b5cd19286055b20214ab5e5c809d171e0a2be Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 19 Aug 2025 21:42:43 +0200 Subject: [PATCH 01/26] start verify-release.sh with bash --- src/changelog/3.2.0/.release.xml | 2 +- src/site/antora/modules/ROOT/pages/release-review.adoc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/changelog/3.2.0/.release.xml b/src/changelog/3.2.0/.release.xml index b1a9979b..a41b9803 100644 --- a/src/changelog/3.2.0/.release.xml +++ b/src/changelog/3.2.0/.release.xml @@ -2,5 +2,5 @@ diff --git a/src/site/antora/modules/ROOT/pages/release-review.adoc b/src/site/antora/modules/ROOT/pages/release-review.adoc index dfc03bf4..17cd5812 100644 --- a/src/site/antora/modules/ROOT/pages/release-review.adoc +++ b/src/site/antora/modules/ROOT/pages/release-review.adoc @@ -98,7 +98,8 @@ wget --cut-dirs=6 \ + [source,bash] ---- -& ./verify-release.sh +bash ./verify-release.sh +cd src ---- + [%collapsible] From c3f92ba0cbca802573dd74843ca2528d9f6ef8f1 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 19 Aug 2025 22:48:17 +0200 Subject: [PATCH 02/26] change dockerfile from mono:latest to ubuntu:20.04 and install mono manually --- Dockerfile | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b018427e..f39c387b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,21 @@ # MAINTAINER Jan Friedrich -FROM mono:latest +FROM ubuntu:20.04 +ENV DEBIAN_FRONTEND=noninteractive \ + 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/* + +# Check Mono version +RUN mono --version RUN apt-get update \ && apt-get upgrade -y \ From 0225493d8ac2243a8b0828be4b82e54d199b979a Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 19 Aug 2025 23:01:29 +0200 Subject: [PATCH 03/26] added release notes for c3f92ba --- src/changelog/3.2.0/c3f92ba-change-dockerfile.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/changelog/3.2.0/c3f92ba-change-dockerfile.xml diff --git a/src/changelog/3.2.0/c3f92ba-change-dockerfile.xml b/src/changelog/3.2.0/c3f92ba-change-dockerfile.xml new file mode 100644 index 00000000..483f783d --- /dev/null +++ b/src/changelog/3.2.0/c3f92ba-change-dockerfile.xml @@ -0,0 +1,10 @@ + + + + + change dockerfile from mono:latest to ubuntu:20.04 and install mono manually (by @FreeAndNil) + + \ No newline at end of file From 4559fde4bcad8980c7ef5930297a9344d589504f Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Wed, 20 Aug 2025 14:22:53 -0400 Subject: [PATCH 04/26] Update release-review.adoc - Mention PowerShell - Add missing $releaseVersion = ... - Add missing `$` --- src/site/antora/modules/ROOT/pages/release-review.adoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/site/antora/modules/ROOT/pages/release-review.adoc b/src/site/antora/modules/ROOT/pages/release-review.adoc index 17cd5812..b9e4c942 100644 --- a/src/site/antora/modules/ROOT/pages/release-review.adoc +++ b/src/site/antora/modules/ROOT/pages/release-review.adoc @@ -20,12 +20,13 @@ Releases of log4net can be verified with following steps: [#windows] -== Windows +== Windows (PowerShell) . Prerequisites (winget - in case of problems see next section choco) + [source,powershell] ---- +$releaseVersion = ...VerionToValidate... winget install -e --id GnuPG.Gpg4win winget install -e --id Slik.Subversion # or any other subversion client winget install -e --id Mono.Mono @@ -48,8 +49,8 @@ choco install dotnet-8.0-sdk + [source,powershell] ---- -svn co https://dist.apache.org/repos/dist/dev/logging/log4net/{releaseVersion} log4net-{releaseVersion} -pushd log4net-{releaseVersion} +svn co https://dist.apache.org/repos/dist/dev/logging/log4net/${releaseVersion} log4net-${releaseVersion} +pushd log4net-${releaseVersion} ---- . Verify and extract @@ -147,4 +148,4 @@ docker run -it log4net-builder # - build src/log4net.sln # inside the container run dotnet test /logging-log4net/src/log4net.sln ----- \ No newline at end of file +---- From 71631474ed652e3be908a3217f6b272cfb1cded8 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Wed, 20 Aug 2025 22:48:58 +0200 Subject: [PATCH 05/26] added check for unzip moved switch to src from doc to script added Set-ExecutionPolicy to prerequisites added Unblock-File added unzip and subversion to Linux prerequisites --- scripts/verify-release.sh | 7 ++++++ .../modules/ROOT/pages/release-review.adoc | 22 ++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index da6720a1..d1b3b9b8 100644 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -1,6 +1,11 @@ #!/bin/bash set -e +if ! which unzip >/dev/null 2>&1; then + echo "The 'unzip' utility is required, but was not found in your path" >&2 + exit 1 +fi + TARGET_DIR="$1"; if test -z "$TARGET_DIR"; then TARGET_DIR="$(pwd)" @@ -17,3 +22,5 @@ done mkdir -p src cd src unzip -q -o ../*source*.zip + +cd src \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/pages/release-review.adoc b/src/site/antora/modules/ROOT/pages/release-review.adoc index b9e4c942..a7f8efd1 100644 --- a/src/site/antora/modules/ROOT/pages/release-review.adoc +++ b/src/site/antora/modules/ROOT/pages/release-review.adoc @@ -26,7 +26,8 @@ Releases of log4net can be verified with following steps: + [source,powershell] ---- -$releaseVersion = ...VerionToValidate... +# as administrator +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned winget install -e --id GnuPG.Gpg4win winget install -e --id Slik.Subversion # or any other subversion client winget install -e --id Mono.Mono @@ -49,6 +50,7 @@ choco install dotnet-8.0-sdk + [source,powershell] ---- +$releaseVersion = ...VersionToValidate... svn co https://dist.apache.org/repos/dist/dev/logging/log4net/${releaseVersion} log4net-${releaseVersion} pushd log4net-${releaseVersion} ---- @@ -57,6 +59,7 @@ pushd log4net-${releaseVersion} + [source,powershell] ---- +Unblock-File ./verify-release.ps1 & ./verify-release.ps1 ---- @@ -72,6 +75,14 @@ dotnet test ./src/log4net.sln [#linux] == Linux +. Prerequisites ++ +[source,bash] +---- +sudo apt install unzip -y +sudo apt install subversion -y +---- + . Check out the release distribution: + [source,bash] @@ -100,7 +111,6 @@ wget --cut-dirs=6 \ [source,bash] ---- bash ./verify-release.sh -cd src ---- + [%collapsible] @@ -131,8 +141,10 @@ for sigFile in *.asc; do gpg --verify $sigFile ${sigFile%.asc}; done + [source,bash] ---- -umask 0022 -unzip -q *source*.zip -d src +mkdir -p src +cd src +unzip -q -o ../*source*.zip + cd src ---- ==== @@ -148,4 +160,4 @@ docker run -it log4net-builder # - build src/log4net.sln # inside the container run dotnet test /logging-log4net/src/log4net.sln ----- +---- \ No newline at end of file From aab079170989ce80958d070d1bee1a4a0f4755bb Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Thu, 21 Aug 2025 20:43:20 +0200 Subject: [PATCH 06/26] switch to macos-14 - mono no longer works on macos-15 --- .github/workflows/build.yaml | 6 +++--- src/log4net.Tests/log4net.Tests.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6c0a06d1..86205a46 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ macos-latest, ubuntu-22.04, windows-latest ] + os: [ macos-14, ubuntu-22.04, windows-latest ] env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 @@ -53,7 +53,7 @@ jobs: - name: Build run: | dotnet build ./src/log4net.sln - + - name: Test run: | - dotnet test ./src/log4net.sln + dotnet test ./src/log4net.sln \ No newline at end of file diff --git a/src/log4net.Tests/log4net.Tests.csproj b/src/log4net.Tests/log4net.Tests.csproj index b144e43a..4a1895ed 100644 --- a/src/log4net.Tests/log4net.Tests.csproj +++ b/src/log4net.Tests/log4net.Tests.csproj @@ -55,4 +55,4 @@ - + \ No newline at end of file From ed615c93b405cbdf7d92141aa6e2e2e358cb3da8 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Fri, 22 Aug 2025 22:09:41 +0200 Subject: [PATCH 07/26] adjust release date for 3.2.0 --- src/changelog/3.2.0/.release.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/changelog/3.2.0/.release.xml b/src/changelog/3.2.0/.release.xml index a41b9803..b854b1ab 100644 --- a/src/changelog/3.2.0/.release.xml +++ b/src/changelog/3.2.0/.release.xml @@ -2,5 +2,5 @@ From b16ef87c3954f3f2b9d49d6feb28efac206b4fe4 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Fri, 22 Aug 2025 23:02:46 +0200 Subject: [PATCH 08/26] updated version to 3.2.1 --- doc/MailTemplate.Announce.txt | 8 ++-- doc/MailTemplate.Result.txt | 4 +- doc/MailTemplate.txt | 6 +-- package.json | 2 +- pom.xml | 4 +- scripts/build-preview.ps1 | 4 +- scripts/build-release.ps1 | 2 +- src/changelog/3.2.1/.release-notes.adoc.ftl | 41 +++++++++++++++++++++ src/changelog/3.2.1/.release.xml | 7 ++++ src/log4net/log4net.csproj | 2 +- 10 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 src/changelog/3.2.1/.release-notes.adoc.ftl create mode 100644 src/changelog/3.2.1/.release.xml diff --git a/doc/MailTemplate.Announce.txt b/doc/MailTemplate.Announce.txt index 1855ceb1..0de9c5c5 100644 --- a/doc/MailTemplate.Announce.txt +++ b/doc/MailTemplate.Announce.txt @@ -1,10 +1,10 @@ To: announce@apache.org, dev@logging.apache.org -Subject: [ANNOUNCE] Apache log4net 3.2.0 released +Subject: [ANNOUNCE] Apache log4net 3.2.1 released Hi, -the Apache log4net team is pleased to announce the 3.2.0 release. +the Apache log4net team is pleased to announce the 3.2.1 release. For further information (support, download, etc.) see - https://logging.apache.org/log4net/release-notes.html -- https://github.com/apache/logging-log4net/releases/tag/rel%2F3.2.0 -- https://www.nuget.org/packages/log4net/3.2.0 +- https://github.com/apache/logging-log4net/releases/tag/rel%2F3.2.1 +- https://www.nuget.org/packages/log4net/3.2.1 diff --git a/doc/MailTemplate.Result.txt b/doc/MailTemplate.Result.txt index e8818c1e..38e094ed 100644 --- a/doc/MailTemplate.Result.txt +++ b/doc/MailTemplate.Result.txt @@ -1,5 +1,5 @@ To: dev@logging.apache.org -Subject: [RESULT][VOTE] Release Apache Log4net 3.2.0 +Subject: [RESULT][VOTE] Release Apache Log4net 3.2.1 and here is my +1. @@ -9,6 +9,6 @@ I will continue the release process. Jan --------------------------------------------------------------------------------------------------- -This is a vote to release the Apache Log4net 3.2.0. +This is a vote to release the Apache Log4net 3.2.1. ... diff --git a/doc/MailTemplate.txt b/doc/MailTemplate.txt index eaf09174..fa50a629 100644 --- a/doc/MailTemplate.txt +++ b/doc/MailTemplate.txt @@ -1,12 +1,12 @@ To: dev@logging.apache.org -Subject: [VOTE] Release Apache Log4net 3.2.0 +Subject: [VOTE] Release Apache Log4net 3.2.1 -This is a vote to release the Apache Log4net 3.2.0. +This is a vote to release the Apache Log4net 3.2.1. Website: https://logging.staged.apache.org/log4net/release-notes.html GitHub: https://github.com/apache/logging-log4net Commit: -Distribution: https://dist.apache.org/repos/dist/dev/logging/log4net/3.2.0 +Distribution: https://dist.apache.org/repos/dist/dev/logging/log4net/3.2.1 Signing key: 0x7D24496A230E29D6349A99EF583E491578F02D5D Review kit: https://logging.staged.apache.org/log4net/release-review.html diff --git a/package.json b/package.json index e3a67355..b1ad339c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4net", - "version": "3.2.0", + "version": "3.2.1", "description": "Log4Net is a logging framework for .NET", "scripts": { "test": "run-s clean-build test-dotnet run-dotnet-core-tests", diff --git a/pom.xml b/pom.xml index 35f1571c..ebc397f3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - + - - + + \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc index 0a301b04..8a4557a8 100644 --- a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc @@ -71,12 +71,12 @@ The new default is `Escape`. The available modes are: `Escape` (default since 3.3.0):: - Newlines are replaced with the escaped representations `\r` → "\\r" and `\n` → "\\n`. + Newlines are replaced with the escaped representations `\r` → "\\r" and `\n` → "\\n". The message is sent as a single syslog entry. `Split` (default before 3.3.0):: The message is split at each newline into multiple syslog entries. - Combined sequences (\r\n or \n\r) count as a single break. + Combined sequences (\r\n) count as a single break. This matches the behaviour in versions before 3.3.0. `Keep`:: From 2fec026a69494de96cdd8be5d44f022086483bfe Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Mon, 8 Dec 2025 23:05:15 +0100 Subject: [PATCH 22/26] set version in supported-versions.adoc via update-version.ps1 --- antora-playbook.yaml | 2 +- scripts/update-version.ps1 | 1 + src/changelog/3.3.0/.release.xml | 2 +- src/site/antora/modules/ROOT/partials/supported-versions.adoc | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/antora-playbook.yaml b/antora-playbook.yaml index edd2e794..c8951d3f 100644 --- a/antora-playbook.yaml +++ b/antora-playbook.yaml @@ -134,7 +134,7 @@ ui: Please read our privacy policy.

- Apache, log4net, and the Apache feather logo are trademarks or registered trademarks of The Apache Software Foundation. + Apache, log4net, and the Apache logo are trademarks or registered trademarks of The Apache Software Foundation. Other names may be trademarks of their respective owners.

diff --git a/scripts/update-version.ps1 b/scripts/update-version.ps1 index cce71f2d..9c41e969 100644 --- a/scripts/update-version.ps1 +++ b/scripts/update-version.ps1 @@ -53,6 +53,7 @@ Update-TextVersion $PSScriptRoot/../doc/MailTemplate.Announce.txt $OldVersion $N Update-TextVersion $PSScriptRoot/build-preview.ps1 $OldVersion $NewVersion Update-TextVersion $PSScriptRoot/build-release.ps1 $OldVersion $NewVersion Update-XmlVersion $PSScriptRoot/../src/log4net/log4net.csproj $NewVersion '/Project/PropertyGroup/Version' +Update-TextVersion $PSScriptRoot/../src/site/antora/modules/ROOT/partials/supported-versions.adoc $OldVersion $NewVersion $ReleaseNoteXml = ' + Date: Tue, 9 Dec 2025 09:00:02 +0100 Subject: [PATCH 23/26] fixed flaky test Log4Net_WritesLogFile_WithDateAndSizeRoll_Config_Works (#277) - failed when the minute changed unexpectedly - now with completely deterministic dates --- src/Directory.Build.props | 1 + .../Integration/Log4NetIntegrationTests.cs | 62 ++++++++++++++++--- src/log4net.Tests/Integration/MockDateTime.cs | 9 +-- .../log4net.maxsizeroll_date.config | 25 -------- src/log4net.Tests/log4net.Tests.csproj | 17 +---- 5 files changed, 56 insertions(+), 58 deletions(-) delete mode 100644 src/log4net.Tests/Integration/log4net.maxsizeroll_date.config diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7f3ae8bb..370c0eb0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,6 +11,7 @@ true <_SkipUpgradeNetAnalyzersNuGetWarning>true true + en;en-US 4.5.0 diff --git a/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs b/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs index 9725c8eb..313c4eb5 100644 --- a/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs +++ b/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs @@ -23,8 +23,12 @@ using System.Linq; using log4net.Appender; using log4net.Config; +using log4net.Core; +using log4net.Layout; using log4net.Repository; +using log4net.Repository.Hierarchy; using NUnit.Framework; +using LoggerHierarchy = log4net.Repository.Hierarchy.Hierarchy; namespace log4net.Tests.Integration { @@ -172,17 +176,24 @@ public void Log4Net_WritesLogFile_WithMaxSizeRoll_Config_Works() public void Log4Net_WritesLogFile_WithDateAndSizeRoll_Config_Works() { DirectoryInfo logDir = CreateLogDirectory("integrationTestLogDir_maxsizerolldate"); - (ILog log, ILoggerRepository repo) = ArrangeLogger("log4net.maxsizeroll_date.config"); - MockDateTime mockDateTime = new(); - repo.GetAppenders().OfType().First().DateTimeStrategy = mockDateTime; - // Write enough lines to trigger rolling by size and date - for (int i = 1; i < 10000; ++i) + DateTime startDate = new(2025, 12, 08, 15, 55, 50); + MockDateTime mockDateTime = new(startDate); // start at the end of a minute + (ILog log, ILoggerRepository repo) = ArrangeCompositeLogger(mockDateTime); + // distribute 10.000 log entries over 60 seconds + TimeSpan stepIncrement = new(TimeSpan.FromSeconds(60).Ticks / 10000); + // 1000 entries (each 100 bytes) -> ~100KB total - 10 rolls expected - 4 will survive + for (int i = 1; i < 1000; ++i) { - log.Debug($"DateRoll entry {i}"); - if (i % 5000 == 0) - { - mockDateTime.Offset = TimeSpan.FromMinutes(1); // allow time for date to change if needed - } + log.Debug($"DateRoll entry {i:D5}"); + mockDateTime.Now += stepIncrement; + } + // switch to next minute to force date roll + mockDateTime.Now = startDate.AddSeconds(10); + // 1000 entries (each 100 bytes) -> ~100KB total - 10 rolls expected - 4 will survive + for (int i = 1; i < 1000; ++i) + { + log.Debug($"DateRoll entry {i:D5}"); + mockDateTime.Now += stepIncrement; } repo.Shutdown(); // Assert: rolled files exist (date+size pattern) @@ -270,5 +281,36 @@ private static (ILog log, ILoggerRepository repo) ArrangeLogger(string configFil ILog log = LogManager.GetLogger(repo.Name, "IntegrationTestLogger"); return (log, repo); } + + private static (ILog log, ILoggerRepository repo) ArrangeCompositeLogger(RollingFileAppender.IDateTime dateTime) + { + LoggerHierarchy repo = (LoggerHierarchy)LogManager.CreateRepository(Guid.NewGuid().ToString()); + PatternLayout layout = new() { ConversionPattern = "%d{yyyy/MM/dd HH:mm:ss.fff} %m-%M%n" }; + layout.ActivateOptions(); + RollingFileAppender rollingAppender = new() + { + Name = "LogFileAppender", + File = "integrationTestLogDir_maxsizerolldate/.log", + AppendToFile = true, + RollingStyle = RollingFileAppender.RollingMode.Composite, + DatePattern = "HH-mm", + DateTimeStrategy = dateTime, + MaximumFileSize = "10KB", + MaxSizeRollBackups = 3, + StaticLogFileName = false, + CountDirection = 1, + PreserveLogFileNameExtension = true, + LockingModel = new FileAppender.NoLock(), + Layout = layout + }; + rollingAppender.ActivateOptions(); + repo.Configured = true; + Logger logger = (Logger)repo.GetLogger("IntegrationTestLogger"); + logger.Level = Level.Debug; + logger.AddAppender(rollingAppender); + logger.Additivity = false; + return (log: new LogImpl(logger), repo); + } + } } \ No newline at end of file diff --git a/src/log4net.Tests/Integration/MockDateTime.cs b/src/log4net.Tests/Integration/MockDateTime.cs index efa67d7a..f0bd6317 100644 --- a/src/log4net.Tests/Integration/MockDateTime.cs +++ b/src/log4net.Tests/Integration/MockDateTime.cs @@ -25,13 +25,8 @@ namespace log4net.Tests.Integration; /// /// Mock implementation of /// -internal sealed class MockDateTime : RollingFileAppender.IDateTime +internal sealed class MockDateTime(DateTime now) : RollingFileAppender.IDateTime { - /// - /// Offset to apply to the current time. - /// - internal TimeSpan Offset { get; set; } - /// - public DateTime Now => DateTime.Now + Offset; + public DateTime Now { get; set; } = now; } \ No newline at end of file diff --git a/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config b/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config deleted file mode 100644 index 2ea6bf4c..00000000 --- a/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/log4net.Tests/log4net.Tests.csproj b/src/log4net.Tests/log4net.Tests.csproj index 4a1895ed..368fd27e 100644 --- a/src/log4net.Tests/log4net.Tests.csproj +++ b/src/log4net.Tests/log4net.Tests.csproj @@ -35,22 +35,7 @@ - - Always - - - Always - - - Always - - - Always - - - Always - - + Always From 5640c187a1671599725cbd9ad68936d96df1bcca Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 9 Dec 2025 10:28:25 +0100 Subject: [PATCH 24/26] stabilize TelnetTest --- src/log4net.Tests/Appender/TelnetAppenderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log4net.Tests/Appender/TelnetAppenderTest.cs b/src/log4net.Tests/Appender/TelnetAppenderTest.cs index ea153e76..71b3b093 100644 --- a/src/log4net.Tests/Appender/TelnetAppenderTest.cs +++ b/src/log4net.Tests/Appender/TelnetAppenderTest.cs @@ -93,7 +93,7 @@ void WaitForReceived(int count) { retries++; TestContext.Out.WriteLine($"receiver: waiting for message {count} of client - retry {retries} failed"); - if (retries > 100) + if (retries > 500) { Assert.Fail("Timeout waiting for received messages"); } From 10647645458a0ccce9ebbc5b0fa9b6b42b55332d Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Mon, 15 Dec 2025 15:43:46 +0100 Subject: [PATCH 25/26] completed OutputDebugStringAppenderTest --- .../Appender/OutputDebugAppenderTest.cs | 26 +++++++++---------- .../Appender/OutputDebugStringAppender.cs | 25 +++++++++++++----- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/log4net.Tests/Appender/OutputDebugAppenderTest.cs b/src/log4net.Tests/Appender/OutputDebugAppenderTest.cs index 9659244c..882b1d53 100644 --- a/src/log4net.Tests/Appender/OutputDebugAppenderTest.cs +++ b/src/log4net.Tests/Appender/OutputDebugAppenderTest.cs @@ -19,8 +19,6 @@ using System; -using System.Diagnostics; - using log4net.Appender; using log4net.Config; using log4net.Core; @@ -33,13 +31,12 @@ namespace log4net.Tests.Appender; /// /// Used for internal unit testing the class. /// -/// -/// Used for internal unit testing the class. -/// [TestFixture] [Platform(Include = "Win")] public sealed class OutputDebugStringAppenderTest { + private const string DebugMessage = "Message - Сообщение - הודעה"; + /// /// Verifies that the OutputDebugString api is called by the appender without issues /// @@ -47,8 +44,8 @@ public sealed class OutputDebugStringAppenderTest public void AppendShouldNotCauseAnyErrors() { ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString()); - - OutputDebugStringAppender outputDebugStringAppender = new() + string? lastDebugString = null; + OutputAppender outputDebugStringAppender = new(value => lastDebugString = value) { Layout = new SimpleLayout(), ErrorHandler = new FailOnError() @@ -58,15 +55,18 @@ public void AppendShouldNotCauseAnyErrors() BasicConfigurator.Configure(rep, outputDebugStringAppender); ILog log = LogManager.GetLogger(rep.Name, GetType()); - log.Debug("Message - Сообщение - הודעה"); - - // need a way to check that the api is actually called and the string is properly marshalled. + log.Debug(DebugMessage); + Assert.That(lastDebugString, Is.Not.Null.And.Contains(DebugMessage)); } } -class FailOnError : IErrorHandler +file sealed class OutputAppender(Action outputDebugString) + : OutputDebugStringAppender(outputDebugString) +{ } + +file sealed class FailOnError : IErrorHandler { public void Error(string message, Exception? e, ErrorCode errorCode) => Assert.Fail($"Unexpected error: {message} exception: {e}, errorCode: {errorCode}"); public void Error(string message, Exception e) => Assert.Fail($"Unexpected error: {message} exception: {e}"); - public void Error(string message) => Assert.Fail($"Unexpected error: {message}"); -} + public void Error(string message) => Assert.Fail($"Unexpected error: {message}"); +} \ No newline at end of file diff --git a/src/log4net/Appender/OutputDebugStringAppender.cs b/src/log4net/Appender/OutputDebugStringAppender.cs index 83e83da7..44442e9d 100644 --- a/src/log4net/Appender/OutputDebugStringAppender.cs +++ b/src/log4net/Appender/OutputDebugStringAppender.cs @@ -17,20 +17,33 @@ // #endregion -using System.Runtime.InteropServices; - +using System; using log4net.Core; using log4net.Util; namespace log4net.Appender; /// -/// Appends log events to the OutputDebugString system. +/// Appends log events to the OutputDebugString system /// /// Nicko Cadell /// Gert Driesen public class OutputDebugStringAppender : AppenderSkeleton { + private readonly Action _outputDebugString; + + /// + public OutputDebugStringAppender() + : this(null) + { } + + /// + /// Constructor for unit testing + /// + /// replacement for + protected OutputDebugStringAppender(Action? outputDebugString) + => _outputDebugString = outputDebugString ?? NativeMethods.OutputDebugString; + /// /// Writes the logging event to the output debug string API /// @@ -40,13 +53,13 @@ public class OutputDebugStringAppender : AppenderSkeleton protected override void Append(LoggingEvent loggingEvent) { #if NETSTANDARD2_0_OR_GREATER - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { - throw new System.PlatformNotSupportedException("OutputDebugString is only available on Windows"); + throw new PlatformNotSupportedException("OutputDebugString is only available on Windows"); } #endif - NativeMethods.OutputDebugString(RenderLoggingEvent(loggingEvent)); + _outputDebugString(RenderLoggingEvent(loggingEvent)); } /// From 42a77cb05abfd5b1ad7224f0861a932eac95c7c8 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Tue, 17 Feb 2026 08:59:08 +0100 Subject: [PATCH 26/26] #280 harden the handling of invalid characters for the XmlLayout classes --- .../3.3.0/280-harden-invalid-characters.xml | 10 ++++ .../Layout/XmlLayoutSchemaLog4jTest.cs | 53 ++++++++++++++++++- src/log4net.Tests/Layout/XmlLayoutTest.cs | 50 +++++++++++++++++ src/log4net.Tests/Util/TransformTest.cs | 2 +- .../Layout/Internal/XmlWriterExtensions.cs | 15 +++++- src/log4net/Layout/XmlLayout.cs | 45 ++++++++-------- src/log4net/Layout/XmlLayoutSchemaLog4j.cs | 38 +++++++------ src/log4net/Util/Transform.cs | 5 +- 8 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 src/changelog/3.3.0/280-harden-invalid-characters.xml diff --git a/src/changelog/3.3.0/280-harden-invalid-characters.xml b/src/changelog/3.3.0/280-harden-invalid-characters.xml new file mode 100644 index 00000000..512ab807 --- /dev/null +++ b/src/changelog/3.3.0/280-harden-invalid-characters.xml @@ -0,0 +1,10 @@ + + + + + harden the handling of invalid characters for the XmlLayout classes (by @FreeAndNil in https://github.com/apache/logging-log4net/pull/280[#280]) + + \ No newline at end of file diff --git a/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs b/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs index d7d14de0..06587783 100644 --- a/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs +++ b/src/log4net.Tests/Layout/XmlLayoutSchemaLog4jTest.cs @@ -18,12 +18,11 @@ #endregion using System; - using log4net.Config; +using log4net.Core; using log4net.Layout; using log4net.Repository; using log4net.Tests.Appender; - using NUnit.Framework; namespace log4net.Tests.Layout @@ -63,5 +62,55 @@ void ThrowAndLog(int foo) } } } + + /// + /// Tests the serialization of invalid characters in the Properties dictionary + /// + [Test] + public void InvalidCharacterTest() + { + StringAppender stringAppender = new() { Layout = new XmlLayoutSchemaLog4J() }; + + ILoggerRepository repository = LogManager.CreateRepository(Guid.NewGuid().ToString()); + BasicConfigurator.Configure(repository, stringAppender); + ILog log = LogManager.GetLogger(repository.Name, "TestLogger"); + + Log(); + + string logEventXml = stringAppender.GetString(); + Assert.That(logEventXml, Does.Contain("us?er")); + Assert.That(logEventXml, Does.Contain("A?B")); + Assert.That(logEventXml, Does.Contain("Log?ger")); + Assert.That(logEventXml, Does.Contain("Thread?Name")); + Assert.That(logEventXml, Does.Contain("Do?main")); + Assert.That(logEventXml, Does.Contain("Ident?ity")); + Assert.That(logEventXml, Does.Contain("User?Name")); + Assert.That(logEventXml, Does.Contain("Mess?age")); + Assert.That(logEventXml, Does.Contain("oh?my")); + + void Log() + { + // Build a LoggingEvent with an XML invalid character in a property value + LoggingEventData data = new() + { + LoggerName = "Log\u0001ger", + Level = Level.Info, + TimeStampUtc = DateTime.UtcNow, + ThreadName = "Thread\u0001Name", + Domain = "Do\u0001main", + Identity = "Ident\u0001ity", + UserName = "User\u0001Name", + Message = "Mess\u0001age", + ExceptionString = "oh\u0001my", + Properties = new() + }; + + // Value contains U+0001 which is illegal in XML 1.0 + data.Properties["us\u0001er"] = "A\u0001B"; + + // Log the event + log.Logger.Log(new(null, repository, data)); + } + } } } \ No newline at end of file diff --git a/src/log4net.Tests/Layout/XmlLayoutTest.cs b/src/log4net.Tests/Layout/XmlLayoutTest.cs index 2f323a3b..3c0dc79d 100644 --- a/src/log4net.Tests/Layout/XmlLayoutTest.cs +++ b/src/log4net.Tests/Layout/XmlLayoutTest.cs @@ -371,4 +371,54 @@ void Bar(int foo) Assert.That(sub, Does.Not.Contain(">")); } } + + /// + /// Tests the serialization of invalid characters in the Properties dictionary + /// + [Test] + public void InvalidCharacterTest() + { + StringAppender stringAppender = new() { Layout = new XmlLayout() }; + + ILoggerRepository repository = LogManager.CreateRepository(Guid.NewGuid().ToString()); + BasicConfigurator.Configure(repository, stringAppender); + ILog log = LogManager.GetLogger(repository.Name, "TestLogger"); + + Log(); + + string logEventXml = stringAppender.GetString(); + Assert.That(logEventXml, Does.Contain("us?er")); + Assert.That(logEventXml, Does.Contain("A?B")); + Assert.That(logEventXml, Does.Contain("Log?ger")); + Assert.That(logEventXml, Does.Contain("Thread?Name")); + Assert.That(logEventXml, Does.Contain("Do?main")); + Assert.That(logEventXml, Does.Contain("Ident?ity")); + Assert.That(logEventXml, Does.Contain("User?Name")); + Assert.That(logEventXml, Does.Contain("Mess?age")); + Assert.That(logEventXml, Does.Contain("oh?my")); + + void Log() + { + // Build a LoggingEvent with an XML invalid character in a property value + LoggingEventData data = new() + { + LoggerName = "Log\u0001ger", + Level = Level.Info, + TimeStampUtc = DateTime.UtcNow, + ThreadName = "Thread\u0001Name", + Domain = "Do\u0001main", + Identity = "Ident\u0001ity", + UserName = "User\u0001Name", + Message = "Mess\u0001age", + ExceptionString = "oh\u0001my", + Properties = new() + }; + + // Value contains U+0001 which is illegal in XML 1.0 + data.Properties["us\u0001er"] = "A\u0001B"; + + // Log the event + log.Logger.Log(new(null, repository, data)); + } + } } \ No newline at end of file diff --git a/src/log4net.Tests/Util/TransformTest.cs b/src/log4net.Tests/Util/TransformTest.cs index 78b106cd..b0e932ac 100644 --- a/src/log4net.Tests/Util/TransformTest.cs +++ b/src/log4net.Tests/Util/TransformTest.cs @@ -40,4 +40,4 @@ public void MaskXmlInvalidCharactersMasks0Char() const string c = "\0"; Assert.That(Transform.MaskXmlInvalidCharacters(c, "?"), Is.EqualTo("?")); } -} +} \ No newline at end of file diff --git a/src/log4net/Layout/Internal/XmlWriterExtensions.cs b/src/log4net/Layout/Internal/XmlWriterExtensions.cs index e08b759d..91c46070 100644 --- a/src/log4net/Layout/Internal/XmlWriterExtensions.cs +++ b/src/log4net/Layout/Internal/XmlWriterExtensions.cs @@ -32,10 +32,11 @@ namespace log4net.Layout.Internal; internal static partial class XmlWriterExtensions { #if NETSTANDARD2_0_OR_GREATER - private static readonly XmlWriterSettings _settings = new XmlWriterSettings + private static readonly XmlWriterSettings _settings = new() { Indent = false, - OmitXmlDeclaration = true + OmitXmlDeclaration = true, + CheckCharacters = false }; #endif @@ -71,4 +72,14 @@ internal static XmlWriter CreateXmlWriter(TextWriter writer) Namespaces = false }; #endif + + /// + /// writes the attribute and replaces invalid characters + /// + internal static void WriteAttributeStringSafe(this XmlWriter writer, string localName, string? value, string mask) + { + if (string.IsNullOrEmpty(value)) + return; + writer.WriteAttributeString(localName, Transform.MaskXmlInvalidCharacters(value!, mask)); + } } \ No newline at end of file diff --git a/src/log4net/Layout/XmlLayout.cs b/src/log4net/Layout/XmlLayout.cs index 0233808c..90b59c1c 100644 --- a/src/log4net/Layout/XmlLayout.cs +++ b/src/log4net/Layout/XmlLayout.cs @@ -192,28 +192,30 @@ public override void ActivateOptions() protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) { writer.EnsureNotNull().WriteStartElement(_eventElementName, Prefix, DefaultEventElementName, Prefix); - writer.WriteAttributeString(LoggerAttributeName, loggingEvent.EnsureNotNull().LoggerName!); + writer.WriteAttributeStringSafe(LoggerAttributeName, loggingEvent.EnsureNotNull().LoggerName, InvalidCharReplacement); - writer.WriteAttributeString(TimestampAttributeName, XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local)); + writer.WriteAttributeStringSafe(TimestampAttributeName, + XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local), + InvalidCharReplacement); if (loggingEvent.Level is not null) { - writer.WriteAttributeString(LevelAttributeName, loggingEvent.Level.DisplayName); + writer.WriteAttributeStringSafe(LevelAttributeName, loggingEvent.Level.DisplayName, InvalidCharReplacement); } - writer.WriteAttributeString(ThreadAttributeName, loggingEvent.ThreadName!); + writer.WriteAttributeStringSafe(ThreadAttributeName, loggingEvent.ThreadName!, InvalidCharReplacement); if (loggingEvent.Domain is not null && loggingEvent.Domain.Length > 0) { - writer.WriteAttributeString(DomainAttributeName, loggingEvent.Domain); + writer.WriteAttributeStringSafe(DomainAttributeName, loggingEvent.Domain, InvalidCharReplacement); } if (loggingEvent.Identity is not null && loggingEvent.Identity.Length > 0) { - writer.WriteAttributeString(IdentityAttributeName, loggingEvent.Identity); + writer.WriteAttributeStringSafe(IdentityAttributeName, loggingEvent.Identity, InvalidCharReplacement); } if (loggingEvent.UserName.Length > 0) { - writer.WriteAttributeString(UsernameAttributeName, loggingEvent.UserName); + writer.WriteAttributeStringSafe(UsernameAttributeName, loggingEvent.UserName, InvalidCharReplacement); } // Append the message text @@ -222,13 +224,13 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) writer.WriteStartElement(_messageElementName, Prefix, DefaultMessageElementName, Prefix); if (!Base64EncodeMessage) { - Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, InvalidCharReplacement); + writer.WriteEscapedXmlString(loggingEvent.RenderedMessage, InvalidCharReplacement); } else { byte[] messageBytes = Encoding.UTF8.GetBytes(loggingEvent.RenderedMessage); string base64Message = Convert.ToBase64String(messageBytes, 0, messageBytes.Length); - Transform.WriteEscapedXmlString(writer, base64Message, InvalidCharReplacement); + writer.WriteEscapedXmlString(base64Message, InvalidCharReplacement); } writer.WriteEndElement(); } @@ -242,22 +244,17 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) foreach (KeyValuePair entry in properties) { writer.WriteStartElement(_dataElementName, Prefix, DefaultDataElementName, Prefix); - writer.WriteAttributeString(NameAttributeName, Transform.MaskXmlInvalidCharacters(entry.Key, InvalidCharReplacement)); + writer.WriteAttributeStringSafe(NameAttributeName, entry.Key, InvalidCharReplacement); // Use an ObjectRenderer to convert the object to a string if (loggingEvent.Repository is not null) { - string valueStr; - if (!Base64EncodeProperties) + string valueStr = loggingEvent.Repository.RendererMap.FindAndRender(entry.Value); + if (Base64EncodeProperties) { - valueStr = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value), InvalidCharReplacement); + valueStr = Convert.ToBase64String(Encoding.UTF8.GetBytes(valueStr)); } - else - { - byte[] propertyValueBytes = Encoding.UTF8.GetBytes(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value)); - valueStr = Convert.ToBase64String(propertyValueBytes, 0, propertyValueBytes.Length); - } - writer.WriteAttributeString(ValueAttributeName, valueStr); + writer.WriteAttributeStringSafe(ValueAttributeName, valueStr, InvalidCharReplacement); } writer.WriteEndElement(); @@ -270,7 +267,7 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) { // Append the stack trace line writer.WriteStartElement(_exceptionElementName, Prefix, DefaultExceptionElementName, Prefix); - Transform.WriteEscapedXmlString(writer, exceptionStr, InvalidCharReplacement); + writer.WriteEscapedXmlString(exceptionStr, InvalidCharReplacement); writer.WriteEndElement(); } @@ -279,10 +276,10 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) if (loggingEvent.LocationInformation is LocationInfo locationInfo) { writer.WriteStartElement(_locationElementName, Prefix, DefaultLocationElementName, Prefix); - writer.WriteAttributeString(ClassAttributeName, locationInfo.ClassName!); - writer.WriteAttributeString(MethodAttributeName, locationInfo.MethodName); - writer.WriteAttributeString(FileAttributeName, locationInfo.FileName!); - writer.WriteAttributeString(LineAttributeName, locationInfo.LineNumber); + writer.WriteAttributeStringSafe(ClassAttributeName, locationInfo.ClassName, InvalidCharReplacement); + writer.WriteAttributeStringSafe(MethodAttributeName, locationInfo.MethodName, InvalidCharReplacement); + writer.WriteAttributeStringSafe(FileAttributeName, locationInfo.FileName, InvalidCharReplacement); + writer.WriteAttributeStringSafe(LineAttributeName, locationInfo.LineNumber, InvalidCharReplacement); writer.WriteEndElement(); } } diff --git a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs index ff2df82f..c8f1636f 100644 --- a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs +++ b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs @@ -47,8 +47,7 @@ public class XmlLayoutSchemaLog4J : XmlLayoutBase /// Constructs an XMLLayoutSchemaLog4j /// public XmlLayoutSchemaLog4J() - { - } + { } /// /// Constructs an XMLLayoutSchemaLog4j. @@ -67,9 +66,9 @@ public XmlLayoutSchemaLog4J() /// appender as well. /// /// - public XmlLayoutSchemaLog4J(bool locationInfo) : base(locationInfo) - { - } + public XmlLayoutSchemaLog4J(bool locationInfo) + : base(locationInfo) + { } /// /// The version of the log4j schema to use. @@ -137,8 +136,7 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) } // translate appdomain name - if (loggingEvent.LookupProperty("log4japp") is null - && loggingEvent.Domain?.Length > 0) + if (loggingEvent.LookupProperty("log4japp") is null && loggingEvent.Domain?.Length > 0) { loggingEvent.GetProperties()["log4japp"] = loggingEvent.Domain; } @@ -159,7 +157,7 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) // Write the start element writer.EnsureNotNull().WriteStartElement("log4j:event", "log4j", "event", "log4net"); - writer.WriteAttributeString("logger", loggingEvent.LoggerName); + writer.WriteAttributeStringSafe("logger", loggingEvent.LoggerName, InvalidCharReplacement); // Calculate the timestamp as the number of milliseconds since january 1970 // @@ -168,18 +166,18 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) // caused by daylight savings time transitions. TimeSpan timeSince1970 = loggingEvent.TimeStampUtc - _sDate1970; - writer.WriteAttributeString("timestamp", XmlConvert.ToString((long)timeSince1970.TotalMilliseconds)); + writer.WriteAttributeStringSafe("timestamp", XmlConvert.ToString((long)timeSince1970.TotalMilliseconds), InvalidCharReplacement); if (loggingEvent.Level is not null) { - writer.WriteAttributeString("level", loggingEvent.Level.DisplayName); + writer.WriteAttributeStringSafe("level", loggingEvent.Level.DisplayName, InvalidCharReplacement); } - writer.WriteAttributeString("thread", loggingEvent.ThreadName); + writer.WriteAttributeStringSafe("thread", loggingEvent.ThreadName, InvalidCharReplacement); // Append the message text if (loggingEvent.RenderedMessage is not null) { writer.WriteStartElement("log4j:message", "log4j", "message", "log4net"); - Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, InvalidCharReplacement); + writer.WriteEscapedXmlString(loggingEvent.RenderedMessage, InvalidCharReplacement); writer.WriteEndElement(); } @@ -190,7 +188,7 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) { // Append the NDC text writer.WriteStartElement("log4j:NDC", "log4j", "NDC", "log4net"); - Transform.WriteEscapedXmlString(writer, valueStr!, InvalidCharReplacement); + writer.WriteEscapedXmlString(valueStr!, InvalidCharReplacement); writer.WriteEndElement(); } } @@ -203,13 +201,13 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) foreach (KeyValuePair entry in properties) { writer.WriteStartElement("log4j:data", "log4j", "data", "log4net"); - writer.WriteAttributeString("name", entry.Key); + writer.WriteAttributeStringSafe("name", entry.Key, InvalidCharReplacement); // Use an ObjectRenderer to convert the object to a string string? valueStr = loggingEvent.Repository?.RendererMap.FindAndRender(entry.Value); if (!string.IsNullOrEmpty(valueStr)) { - writer.WriteAttributeString("value", valueStr); + writer.WriteAttributeStringSafe("value", valueStr, InvalidCharReplacement); } writer.WriteEndElement(); @@ -222,7 +220,7 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) { // Append the stack trace line writer.WriteStartElement("log4j:throwable", "log4j", "throwable", "log4net"); - Transform.WriteEscapedXmlString(writer, exceptionStr!, InvalidCharReplacement); + writer.WriteEscapedXmlString(exceptionStr!, InvalidCharReplacement); writer.WriteEndElement(); } @@ -231,10 +229,10 @@ protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) if (loggingEvent.LocationInformation is LocationInfo locationInfo) { writer.WriteStartElement("log4j:locationInfo", "log4j", "locationInfo", "log4net"); - writer.WriteAttributeString("class", locationInfo.ClassName); - writer.WriteAttributeString("method", locationInfo.MethodName); - writer.WriteAttributeString("file", locationInfo.FileName); - writer.WriteAttributeString("line", locationInfo.LineNumber); + writer.WriteAttributeStringSafe("class", locationInfo.ClassName, InvalidCharReplacement); + writer.WriteAttributeStringSafe("method", locationInfo.MethodName, InvalidCharReplacement); + writer.WriteAttributeStringSafe("file", locationInfo.FileName, InvalidCharReplacement); + writer.WriteAttributeStringSafe("line", locationInfo.LineNumber, InvalidCharReplacement); writer.WriteEndElement(); } } diff --git a/src/log4net/Util/Transform.cs b/src/log4net/Util/Transform.cs index 6ff80086..b646974b 100644 --- a/src/log4net/Util/Transform.cs +++ b/src/log4net/Util/Transform.cs @@ -26,9 +26,6 @@ namespace log4net.Util; /// Utility class for transforming strings. /// /// -/// -/// Utility class for transforming strings. -/// /// /// Nicko Cadell /// Gert Driesen @@ -46,7 +43,7 @@ public static class Transform /// or using CDATA sections. /// /// - public static void WriteEscapedXmlString(XmlWriter writer, string textData, string invalidCharReplacement) + public static void WriteEscapedXmlString(this XmlWriter writer, string textData, string invalidCharReplacement) { writer.EnsureNotNull(); string stringData = MaskXmlInvalidCharacters(textData, invalidCharReplacement);