Release notes

3.4.0

Release date

2026-08-21

Added

  • Add a MailKit based SmtpAppender in the new log4net.Ext.Mail assembly and mark log4net.Appender.SmtpAppender as obsolete, because Microsoft no longer recommends System.Net.Mail.SmtpClient for new development (requested by @DietzeC, implemented by @FreeAndNil in #302) (300, 302)

  • pin the Maven wrapper and the Maven distribution it downloads with wrapperSha256Sum and distributionSha256Sum. mvnw and MavenWrapperDownloader already refuse to run when a download does not match, but neither property was set, so whatever the URLs returned was executed (CWE-494). Both values were taken from artifacts whose PGP signature verifies against the Apache Maven KEYS (audit 1231d72-f023) (310)

  • add a TransportSecurity option to the log4net.Ext.Mail SmtpAppender and make EnableSsl a shorthand for it. EnableSsl requires transport security, selecting implicit TLS on port 465 and mandatory STARTTLS elsewhere, so connecting fails when the server offers no TLS instead of silently continuing in plaintext, as System.Net.Mail.SmtpClient.EnableSsl does. Opportunistic STARTTLS remains available, but only by asking for it with TransportSecurity=StartTlsWhenAvailable (audit 1231d72-f007) (310)

  • add ListenAddress to TelnetAppender. The listening socket was bound to IPAddress.Any with no way to scope it, so an operator who only wanted to watch the log from the machine itself still got a listener on every interface. The default is unchanged, and the listening socket now follows the address family, so an IPv6 address works too (audit 1231d72-f002) (310)

  • log an error when AdoNetAppender is activated without CommandText. In that legacy mode the rendered Layout output is executed as the SQL statement, and because layouts perform no SQL quoting, logged content becomes part of the statement (CWE-89). Configure CommandText with AdoNetAppenderParameter bindings instead (audit 1231d72-f003) (310)

Changed

  • 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 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 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 (305)

  • 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 (305)

  • 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 (305)

  • bound the waits for the named mutexes used by FileAppender.InterProcessLock and by RollingFileAppender when it decides whether to roll. Both waited without a timeout while the appender lock was held, so a mutex nobody released suspended every thread logging through the appender. The wait now stops after LockTimeoutMillis, 10000 by default, reporting through the error handler and dropping the event or skipping the roll check; Timeout.Infinite restores the previous behaviour. AbandonedMutexException is handled as the successful acquisition it is, rather than leaving the mutex held, and the rolling lock is no longer released when it was never taken (audit 1231d72-f015) (310)

  • give RegexToMatch matching a deadline in StringMatchFilter and the filters deriving from it, PropertyFilter, MdcFilter and NdcFilter. The pattern was matched with Regex.InfiniteMatchTimeout while the appender lock was held, so a pattern that backtracks could stall everything logging through the appender on some inputs. Matching now stops after MatchTimeoutMillis, 1000 by default, and an abandoned match is reported once and leaves the event to the rest of the filter chain; 0 restores unbounded matching. The pattern comes from configuration and is trusted, so this is hardening rather than a vulnerability fix (audit 1231d72-f013) (310)

  • report the first error of an appender even when log4net.Internal.Debug is off, which is the default. OnlyOnceErrorHandler is the default error handler of every appender, so an appender that stopped delivering events previously did so without leaving any trace (CWE-778). log4net.Internal.Quiet and LogLog.EmitInternalMessages remain the ways to silence internal messages (audit 1231d72-f019) (310)

Fixed

  • fix LoggingEvent.UserName resolving the Windows identity for every event, because the cache added in 2.0.15 was held in an instance field and so never applied. The process identity is now resolved once, and impersonated identities once per user, cutting a buffered FixFlags.All event from about 193 us to 17.5 us on the machine measured (304)

  • 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 (305)

  • 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 (305)

  • Make log4net usable from a PublishAot build, where LogManager.GetLogger() used to throw PlatformNotSupportedException from Assembly.GetCallingAssembly(), and where repositories and pattern converters were left without a constructor by the trimmer. Configuration has to be done in code - see the new Native AOT and trimming page (reported by @vpenades, implemented by @FreeAndNil in #306) (233, 306)

  • stop a single logging event that the database rejects from discarding the whole buffer in AdoNetAppender. The events have already been removed from the buffer when they are sent, so a rolled back transaction lost up to BufferSize unrelated events, which an attacker could trigger repeatedly with content the provider refuses, such as U+0000 on npgsql (CWE-778). Delivery becomes at-least-once: events already applied by a batch that then failed may be written again (audit 1231d72-f004) (310)

  • stop a NUL character in logged content from truncating LocalSyslogAppender records. The message is marshaled to libc as a null-terminated string, so everything the layout rendered after the NUL was silently dropped, including trailing fields and exception text (CWE-158). NUL is now escaped as \0; other control characters are still passed through, because syslog(3) encodes them and newlines are needed for multi-line exception output (audit 1231d72-f008) (310)

  • make TextWriterAppender and the appenders deriving from it, including FileAppender and RollingFileAppender, flush under the appender lock. Flush synchronized on a private object while Append runs under the lock taken by DoAppend, so a flush could run concurrently with a write to the same QuietTextWriter, which is not thread safe, and interleave or lose output. Flush also returned true whatever happened and let a failure from the underlying writer escape to its caller; it now reports through the ErrorHandler and returns false (audit 1231d72-f020) (310)

  • stop AdoNetAppender from repeating the password when it reports a connection it could not open. The message named the resolved connection string in full, and the documented examples embed Password=…​ (CWE-532). Password-bearing keywords are now replaced with *, while the rest of the connection string is kept so the message stays useful (audit 1231d72-f018) (310)

  • report a RemoteSyslogAppender Identity that renders control characters, and remove them, so that it cannot split the record. The identity becomes the TAG of the syslog record and was appended verbatim, while the message part is filtered, so a line feed in it let the text that followed be read as a record of its own with an attacker chosen facility and severity. The TAG is a structural identifier and expected to be a constant, so this is reported as the configuration error it is rather than being repaired quietly (audit 1231d72-f016) (310)

  • fix the lifetime of the LocalSyslogAppender identity. openlog keeps the pointer it is given rather than a copy of the string, and registers it for the process rather than for an appender. Each ActivateOptions allocated a new buffer and forgot the previous one, leaking it (CWE-401), while OnClose freed a buffer that another instance may still have been logging through. The handle is now shared, replaced under a lock once openlog points at the new string, and left allocated when an appender closes (audit 1231d72-f017) (310)

  • stop a Telnet client that connects and then stops reading from suspending all logging. TelnetAppender writes to its clients while the appender lock is held and set no Socket.SendTimeout, so once TCP flow control filled the client’s receive window the next write blocked forever and every thread logging through the appender queued behind it (CWE-833). Writes now time out after SendTimeoutMillis (5000 by default) and the client is disconnected; 0 restores the previous unbounded behavior (audit 1231d72-f001) (310)

  • stop LoggingEvent.UserName reporting the wrong user for an event logged while impersonating. The property resolved the identity of whichever thread read it, so with a buffering appender the buffered events were attributed to the thread flushing the buffer rather than the one that logged them (CWE-282). An event logged while impersonating now takes its user name with it when it is fixed, and an event read on an impersonating thread reports the not available text instead of that thread’s user. Without impersonation the name is the process identity, which is the same on every thread, so it is still resolved lazily and nothing changes (audit 1231d72-f014) (310)

  • make the release verification scripts fail closed. verify-release.ps1 reported a SHA-512 mismatch with -ErrorAction Continue and never checked the exit code of gpg, so it extracted the archive and exited 0 for a tampered artifact or a broken signature. Both scripts looped over whichever .asc files were present, so deleting them left nothing to verify and the scripts succeeded. The checks are now driven from the artifacts, each of which must have a .sha512 and a .asc, and the keys are imported into a key ring of their own so that a signature from any other key this machine trusts is no longer accepted (audit 1231d72-f009, 1231d72-f010) (310)

Removed

  • remove the git-broadcast workflow. Its push and pull request triggers had been commented out, leaving it dispatched by hand only, while it still ran npx git-broadcast@beta, a dist-tag that can be repointed at any published version, in a job that checks out with a token able to push to this repository, using mutable action tags and no permissions block (CWE-1357) (audit 1231d72-f011) (310)

3.3.2

Release date

2026-06-25

Fixed

  • fix dropped log messages during concurrent GetLogger calls (reported by @omtslug, fixed by @gdziadkiewicz in #294) (292, 294)

  • fix race condition in AppenderSkeleton.AddFilter and ClearFilters under concurrent logging (CWE-362) (f57d7b3-001, 298)

  • fix unhandled exception in AppenderSkeleton finalizer that could terminate the process when OnClose() throws (CWE-755) (f57d7b3-003, 298)

  • fix mutex leak in InterProcessLock.AcquireLock when the underlying file stream is null (CWE-772) (f57d7b3-002, 298)

3.3.1

Release date

2026-04-20

Fixed

  • reduce silent log event loss during XmlConfigurator reconfiguration (by @N0TRE3L in #287) (287)

  • fix silent corruption of supplementary Unicode characters (U+10000–U+10FFFF) in MaskXmlInvalidCharacters: valid UTF-16 surrogate pairs are now preserved instead of being replaced with the mask string (reported by @N0tre3l, fixed by @freeandnil in #291) (290, 291)

3.3.0

Release date

2026-02-20

Changed

Fixed

  • shorten console output for unit tests (by @FreeAndNil in #268) (268)

  • harden the handling of invalid characters for the XmlLayout classes (by @FreeAndNil in #280) (280)

3.2.0

Release date

2025-08-22

Added

  • Asynchronous Sending for RemoteSyslogAppender

    • requested by @yogitasingh001 #255

    • implemented by @yogitasingh001 (in #253) and @FreeAndNil (in #258)

    • Refactored the RemoteSyslogAppender to use an asynchronous, non-blocking logging model. Introduced a background worker pattern using BlockingCollection<byte[]> to decouple UDP log transmission from the calling thread. This eliminates thread blocking caused by .Wait() on SendAsync() and significantly improves performance under high-load conditions. A graceful shutdown ensures any buffered logs are flushed on appender closure. No changes to external behavior - maintains backward compatibility.

Fixed

  • Fix InvalidCastException in NDC.Inherit(System.Collections.Stack) (reported by @jberg7, fixed by @FreeAndNil in #246) (245, 246)

  • Fix caching of time strings in AbsoluteTimeDateFormatter (reported by @LeadAssimilator, fixed by @FreeAndNil in #248) (247, 248)

  • The rolling of logfiles was fixed (reported by @maedula and @mordio, fixed by @gdziadkiewicz) (250, 260, 257, 262)

  • Shutdown: Unsubscribe from AppDomain event handlers (by @Flohack74) (256)

  • change dockerfile from mono:latest to ubuntu:20.04 and install mono manually (by @FreeAndNil) (c3f92ba)

3.1.0

Release date

2025-05-12

Added

  • Migrate the log4net site to Antora and use logging-parent pom (implemented by @FreeAndNil in #210) (209, 210)

  • detect Android and use environment variables instead of AppSettings (reported by @arunjose696, implemented by @FreeAndNil in #240) (239, 240)

Fixed

  • RollingFileAppender does not respect the MaxSizeRollBackups with positive CountDirection (implemented by @gdziadkiewicz in #232) (163, 231, 232, 236)

3.0.4

Release date

2025-02-21

Added

  • Enable automatic test runs on commit (implemented by @gdziadkiewicz in #204) (106, 204)

  • Provide path of expected .config file in error message (requested by @FrankNuessle, implemented by @FreeAndNil in #228) (227, 228)

Fixed

  • Keep old, working ubuntu image version (by @gdziadkiewicz) (215)

  • fixed SetQWForFiles method name in FileAppender from #196 by @sketchturner in #217 (fixes #216 reported by @sketchturner) (216, 217, 196)

  • XmlLayoutSchemaLog4J: Changed XML start element for exceptions since v3.0.0 (reported by @sita-martin, fixed in #226 by @FreeAndNil) (225, 226)

3.0.3

Release date

2024-11-07

Added

  • Enable Microsoft.CodeAnalysis.NetAnalyzers (by @FreeAndNil) (201)

Changed

  • file scoped namespaces and CA warnings fixed in examples (by @FreeAndNil) (202)

  • use raw strings for config files in unit tests (by @FreeAndNil) (203)

  • use raw strings for config files in unit tests (reported by @FerdinandStapenhorst, implemented by @FreeAndNil in #206 (205)

Fixed

  • MDC was accidentally renamed into Mdc in 3.0.2 - this change was reverted (72fdee8)

  • NDC was accidentally renamed into Ndc in 3.0.2 - this change was reverted (72fdee8)

Updated

  • Fix empty string received by .NET 8 users on Linux on userName (by @gdziadkiewicz) (199)

  • Bumped NUnit to 4.2.2 (by @FreeAndNil) (200)

3.0.2

Release date

2024-10-21

Known issues

  • MDC was accidentally renamed into Mdc - this will be reverted in 3.0.3

  • NDC was accidentally renamed into Ndc - this will be reverted in 3.0.3

Added

  • separate review instructions for log4net (by @FreeAndNil) (189)

Changed

  • more modern coding style (by @FreeAndNil) (196)

Fixed

  • ObjectDisposedException in TelnetAppender (3.0.1) reported by @VianneyTremelo, fixed by @FreeAndNil in #195 (194)

  • IndexOutOfRangeException when creating child loggers multithreaded, reported by @FreeAndNil, fixed by @FreeAndNil in #198 (197)

3.0.1

Release date

2024-09-27

Added

  • added source link (by @FreeAndNil) (176)

  • automate version bumps (by @FreeAndNil) (181)

Fixed

  • use ManagedThreadId for long running tasks reported by @Audela-220, fixed by @FreeAndNil in #180 (177)

  • log4net.Util.PatternString not working in 3.0.0 (Unable to set property [file] on object [log4net.Appender.FileAppender]), reported by @sc-mk, fixed by @FreeAndNil in #184 (183)

3.0.0

Release date

2024-09-15

Breaking changes

Starting with 3.0.0 we only support the following target frameworks

  • net462

  • netstandard2.0

The reasoning for this change can be found in #111 - Dropping support for older runtimes

Removed obsolete classes and members

since 1.2.14 (2015)
  • log4net.Appender.BufferingAppenderSkeleton.OnlyFixPartialEventData

  • log4net.Appender.ColoredConsoleAppender.ctor(ILayout)

  • log4net.Appender.ColoredConsoleAppender.ctor(ILayout, bool)

  • log4net.Appender.ConsoleAppender.ctor(ILayout)

  • log4net.Appender.ConsoleAppender.ctor(ILayout, bool)

  • log4net.Appender.DebugAppender.ctor(ILayout)

  • log4net.Appender.EventLogAppender.ctor(ILayout)

  • log4net.Appender.FileAppender.ctor(ILayout, string)

  • log4net.Appender.FileAppender.ctor(ILayout, string, bool)

  • log4net.Appender.MemoryAppender.OnlyFixPartialEventData

  • log4net.Appender.SmtpAppender.LocationInfo

  • log4net.Appender.TextWriterAppender.ctor(ILayout, Stream)

  • log4net.Appender.TextWriterAppender.ctor(ILayout, TextWriter)

  • log4net.Appender.TraceAppender.ctor(ILayout)

  • log4net.Config.DOMConfigurator

  • log4net.Config.AliasDomainAttribute

  • log4net.Config.DomainAttribute

  • log4net.Config.DOMConfiguratorAttribute

  • log4net.Core.LoggerManager.GetLoggerRepository

  • log4net.Core.LoggerManager.CreateDomain

  • log4net.Core.LoggingEventData.TimeStamp

  • log4net.Core.LoggingEvent.GetExceptionStrRep

  • log4net.Core.LoggingEvent.FixVolatileData

  • log4net.LogManager.GetLoggerRepository

  • log4net.LogManager.CreateDomain

since 2.0.6 (2016)
  • log4net.Util.SystemInfo.ProcessStartTime

Sealed classes - the following classes are now sealed

  • log4net.Config.AliasRepositoryAttribute

  • log4net.Config.RepositoryAttribute

  • log4net.Config.XmlConfiguratorAttribute

Added

  • Add support for nullable annotations (by @erikmav) (122, 123, 124, 126, 129, 131, 132, 136, 141)

  • Enable building log4net with docker container (by @FreeAndNil) (127, 128)

Changed

  • log4net.Appender.MemoryAppender.m_eventsList (protected field) is now List<LoggingEvent> (instead of System.Collections.ArrayList) (124)

  • log4net.Util.ReadOnlyPropertiesDictionary.InnerHashtable is now Dictionary<string, object?> (instead of System.Collections.Hashtable) (124)

  • log4net.Repository.Hierarchy.Hierarchy.EmittedNoAppenderWarning is now internal (157)

Fixed

  • Fix dotnet (ie not netfx) test run (by @mobilebilly) (109)

  • Regression: Creating nested loggers in reverse order fails in 3.0.0-preview.1 (by @FreeAndNil) (156)

  • ColoredConsoleAppender writes UTF-8 preamble to the console on initialization (reported by @RoboBurned, fixed by @FreeAndNil) (168)

  • System.NullReferenceException when comparing with a null Level (by @FreeAndNil) (169)

  • Culture-Specific String Comparisons Cause RollingFileAppender Failure in log4net on NET 5+ (reported by @stianeklund) (179)

Removed

  • Dropping support for older runtimes (by @fluffynuts and @FreeAndNil) (111, 115)

  • Remove deprecated code for 3.0 (by @erikmav) (125, 138)

  • log4net.Appender.RemotingAppender (154)

  • log4net.Appender.NetSendAppender (158)

  • Serializable Support (for .netstandard) (175)

2.x

Changed

  • release notes for older releases can be found here (2.x-and-older)