Release notes

3.5.0

Release date

2026-10-01

Added

  • TimeoutDecision on StringMatchFilter and PropertyFilter, deciding an event whose regular expression match was abandoned. An abandoned match was treated as a non-match, but the content decides whether the deadline is reached, so in an AcceptOnMatch allowlist followed by a deny-all it let content suppress its own record. The default stays Neutral; Accept makes such a chain fail towards logging (audit da18b6fd-f017, implemented by @FreeAndNil) (317)

Changed

  • require PowerShell 7.4 in the release scripts. All native command error handling in them rests on $PSNativeCommandUseErrorActionPreference, which exists only from 7.4, so under Windows PowerShell 5.1 a failing gpg --verify was ignored and verify-release.ps1 reported success and exited 0. The scripts now refuse to start on an older host, and the review instructions install PowerShell 7 and run the script with pwsh (audit 1231d72-f009, implemented by @FreeAndNil) (313)

  • send mail from a background thread in the MailKit based SmtpAppender. The mail used to go out while the appender lock was held, so the thread that logged, and every thread behind it, waited for the SMTP server. The queue holds sendQueueSize mails (500) and a logging call waits at most enqueueTimeoutMillis (5000) for room in it. Failures are still reported to the error handler, but after the logging call has returned, and Flush now honours its timeout (audit da18b6fd-f004, implemented by @FreeAndNil) (314)

  • bound a whole send in the MailKit based SmtpAppender to 15 seconds. MailKit waits 100 seconds per operation by default, and the mail goes out while the appender lock is held, so an unresponsive server suspended every thread logging through the appender. SendTimeoutMillis is a deadline for the send as a whole, because a per operation timeout still allows a multiple of itself overall (audit da18b6fd-f004, implemented by @FreeAndNil) (314)

  • bound a single send in SmtpAppender to 15 seconds. SmtpClient.Timeout defaults to 100 seconds and the appender never set it, and the mail goes out while the appender lock is held, so a server that accepted the connection and then stopped answering suspended every thread logging through the appender. Configurable with SendTimeoutMillis (implemented by @FreeAndNil) (314)

  • move SyslogNewLineHandling out of RemoteSyslogAppender to log4net.Appender, now that LocalSyslogAppender uses it too. Configuration files are unaffected, they bind the value by name, but code naming RemoteSyslogAppender.SyslogNewLineHandling has to drop the prefix (implemented by @FreeAndNil) (315)

  • The default MatchTimeoutMillis on StringMatchFilter and PropertyFilter is now 50 instead of 1000. The match runs while the appender lock is held, so the deadline is the bound on how long one crafted event can stall everything logging through the appender. A legitimate match over an event takes a fraction of 50ms; raise the property if a pattern genuinely needs longer (audit da18b6fd-f041, implemented by @FreeAndNil) (317)

  • Write to TelnetAppender clients from a background thread. Clients were written to under the appender lock, so one that stopped reading blocked every thread that logs for sendTimeoutMillis, and the writes are serial, so 20 connected clients cost 20 times that on a single event. Events are queued now, bounded by the new sendQueueSize (500), and a logging call waits at most enqueueTimeoutMillis (50) for room before the event is dropped from the telnet stream, counted and reported. Only the telnet view is lost, never the log. While the queue stays full, enqueueTimeoutMillis caps logging at 20 events per second; set it to 0 to drop immediately instead of waiting (audit da18b6fd-f014, implemented by @FreeAndNil) (318)

  • TelnetAppender now listens on 127.0.0.1 by default instead of every interface. The stream is unauthenticated and unencrypted, so any host that could reach the port could read the application’s log, and every documented example already used loopback. Watching the log from another machine is now opt-in: set listenAddress to 0.0.0.0 or :: to restore the old behaviour. The SocketHandler(port, sendTimeoutMillis) constructor defaults the same way (audit da18b6fd-f012, implemented by @FreeAndNil) (318)

  • The site build now takes its Antora dependencies from the gha/v0 branch of logging-parent, which is where all Apache Logging projects now manage them. The js-yaml override and the committed package-lock.json are gone with it (reported by @ppkarwasz, implemented by @FreeAndNil) (320)

  • Deprecate log4net.Util.TwoArgAction. It existed for the callback the logical context stack used to register itself, which now holds its owning LogicalThreadContextStacks instead, so nothing in log4net uses the delegate. It will be removed in version 4 (implemented by @FreeAndNil) (323)

  • SmtpPickupDirAppender no longer writes a lone dot at the end of the file. The pickup service reads the file to its end, so the terminator was never needed, and it made a logged line that is only a dot look like the end of the mail. Body lines are still written verbatim rather than dot stuffed, so an agent that expects SMTP DATA framing is not a supported consumer (audit da18b6fd-f037, implemented by @FreeAndNil) (324)

  • detect Android from the file system instead of starting getprop, which SystemInfo ran on every Unix host, resolved through PATH, so the first match there decided the answer (audit da18b6fd-f048, fixed by @FreeAndNil) (325)

  • release the FormatMessage buffer in NativeError with LocalFree, the allocator that API uses, instead of letting the marshaller free it as COM task memory. Both hit the process heap on current Windows, so this is hygiene rather than a fix (audit da18b6fd-f045, fixed by @FreeAndNil) (325)

  • require transport security by default in the log4net.Ext.Mail SmtpAppender: its TransportSecurity now defaults to Required instead of None, so body and credentials no longer cross the network in cleartext (CWE-319). A relay without STARTTLS stops working on upgrade; set transportSecurity to None, or enableSsl to false, to keep the old behaviour (audit da18b6fd-f026, implemented by @FreeAndNil) (329)

Fixed

  • Stop reporting log4net:ERROR Exception while reading ConfigurationSettings in a process that hosts the runtime natively, such as powershell.exe or a C++ host of the CoreCLR. There is no entry assembly there for the configuration system to derive the config file path from, so it fails with PlatformNotSupportedException before any config file is read. That is now recognised as an absent configuration system, the same as under Native AOT: it is logged once at debug level and application settings are read from environment variables instead (reported by @viktorgobbi, fixed by @FreeAndNil in #311) (162)

  • keep secrets out of the AdoNetAppender message for a connection it could not open. Hiding password-bearing keywords missed Extended Properties, which nests a whole connection string, and keywords such as AccessToken (CWE-532). Only keywords naming the server and account are kept now (audit da18b6fd-f028, fixed by @FreeAndNil) (313)

  • stop the release verification scripts from importing a KEYS file that came with the artifacts instead of the one they download. wget refuses to overwrite, so a planted KEYS stayed in place and was imported into the verification key ring, and artifacts signed by whoever placed it verified (CWE-347). The scripts now verify in a GnuPG home of their own, filled from a copy downloaded there, rather than with --keyring, which gpg ignores where common.conf sets use-keyboxd. Present in 3.2.0 onward, since the script was added (audit da18b6fd-f003, reported by @swebb2066, fixed by @FreeAndNil) (313)

  • stop AdoNetAppender from retrying a whole batch one event at a time when none of them can be written. Retrying after a rolled back transaction was added in 3.4.0 to save the events around the one the database rejected, but a batch that fails outright, such as a missing table or a missing permission, then cost one round trip per event: 513 instead of 1 for a full buffer. It now gives up after five consecutive failures (fixed by @FreeAndNil) (314)

  • report a buffering appender that is still holding events from Flush. It returned true unconditionally, including for a lossy appender, where flushing deliberately does nothing and every buffered event stays where it was (fixed by @FreeAndNil) (314)

  • bound the queue RemoteSyslogAppender sends from. It was unbounded, so a syslog server that stopped accepting datagrams grew it until the process ran out of memory, and shutdown waited five seconds and then abandoned a drain that had no limit of its own. It now holds sendQueueSize datagrams (500), a logging call waits at most enqueueTimeoutMillis (5000) for room, losses are counted and reported, and Flush honours its timeout (audit da18b6fd-f034, fixed by @FreeAndNil) (314)

  • stop RemoteSyslogAppender from opening a second, unused UDP socket. It sends through the connection its background pump owns, but also inherited one from UdpAppender that nothing ever used, which bound localPort twice. A pump that cannot connect now reports it as well, instead of ending unobserved and leaving every later event queued behind a sender that is gone (audit da18b6fd-f034, fixed by @FreeAndNil) (314)

  • escape NUL characters in EventLogAppender content. ReportEventW takes a null terminated string, so a NUL in logged content ended the stored record there and silently dropped whatever the layout rendered after it, exception text and trailing fields included (CWE-158). WriteEntry raises nothing, so the record simply stored short. Measured on Windows 11 build 26200: of a 45 character message with a NUL at 23, the 23 character prefix was stored and the rest was gone (audit da18b6fd-f007, fixed by @FreeAndNil) (315)

  • stop EventLogAppender truncating to a size the event log then discards. The limit is a whole record budget that the log name, the source and the machine name are spent from, so the fixed 31837 was above the real ceiling: measured on Windows 11 build 26200, a record is stored while message + logName + applicationName stays within 31736 characters, and one character beyond that the service stores nothing and reports nothing. The whole event was lost rather than shortened, and applicationName defaults to the app domain name, so a consumer with a long assembly name lost more. The limit is now computed, and a truncation is reported through the error handler, which is the only signal available (audit da18b6fd-f030, fixed by @FreeAndNil) (315)

  • escape the newlines in logged content in LocalSyslogAppender, which passed them to syslog(3) unchanged. A daemon that writes the message through to a line oriented log then records everything after the newline as its own entry, so content could forge an authentic looking record (CWE-117). NewLineHandling mirrors the option of the same name on RemoteSyslogAppender, which already escaped by default; set it to Keep for the previous behaviour (audit da18b6fd-f008, fixed by @FreeAndNil) (315)

  • escape NUL characters in OutputDebugStringAppender content. OutputDebugStringW takes a null terminated string, so a NUL in logged content ended the record there and silently dropped whatever the layout rendered after it, exception text and trailing fields included (CWE-158). The escape LocalSyslogAppender already applied is now shared between the two (audit da18b6fd-f009, fixed by @FreeAndNil) (315)

  • stop one logging event destroying a whole SmtpPickupDirAppender batch. File.CreateText throws on content it cannot encode, such as an unpaired surrogate, which abandoned every buffered event and left a truncated mail in the pickup directory for the service to send. Such content is now written as a \uXXXX escape (audit da18b6fd-f011, fixed by @FreeAndNil) (315)

  • escape the characters RemoteSyslogAppender cannot send instead of deleting them. RFC 3164 allows only the visible ASCII characters and space, and everything else was dropped silently, so Schönwetter 你好 reached the collector as Schnwetter ` and a tab disappeared from between its neighbours. Such characters are now written as a `\uXXXX escape, which keeps the record inside the allowed range and readable (audit da18b6fd-f035, fixed by @FreeAndNil) (315)

  • stop one logging event disconnecting every TelnetAppender client. The default writer encoding throws on content it cannot encode, such as an unpaired surrogate, and Send reads any failure as a client that hung up. Unpaired surrogates are now written as a \uXXXX escape, as elsewhere (audit da18b6fd-f013, fixed by @FreeAndNil) (315)

  • stop AnsiColorTerminalAppender dropping an event that renders to nothing. The branch meant for a single character read the first one without checking there was one, so an empty render threw and the event was lost. The reset codes are now placed by one computed offset, which has no special case to get wrong (audit da18b6fd-f029, fixed by @FreeAndNil) (316)

  • Stop %aspnet-request losing the whole event for a request that fails ASP.NET request validation. Reading HttpRequest.Params validates the query string, form and cookies on first access, so a request carrying <script> threw inside the layout and the appender discarded the event: a sender could suppress the log record of their own request. The converter now reads through HttpRequest.Unvalidated, which keeps the content instead of dropping it (audit da18b6fd-f019, fixed by @FreeAndNil) (316)

  • Compare StringToMatch ordinally in StringMatchFilter and PropertyFilter. The substring search was culture sensitive, and a linguistic search skips ignorable characters, so content holding a NUL, a soft hyphen or a zero-width space between the letters of the configured text still matched it, and the decision varied with the host culture. The filter now decides the same way a reader of the log would (audit da18b6fd-f018, fixed by @FreeAndNil) (317)

  • Stop a lock the appender could not take from disabling it. Writing the footer, closing the writer and opening the file released the file lock even when taking it had failed, and every later attempt then failed too, so the footer and anything still buffered were dropped without a word. Only a lock that was taken is released now (audit da18b6fd-f032, fixed by @FreeAndNil) (319)

  • Keep logging to a deep path on Unix. The locks that serialise writing and rolling are named after the log file, and Unix rejects a name longer than 255 UTF-8 bytes, which a path of 104 characters can already exceed, so the appender failed to start and nothing was written at all. Such a name is hashed now; shorter ones, and every name on Windows, are unchanged (audit da18b6fd-f010, da18b6fd-f031, fixed by @FreeAndNil) (319)

  • Let two processes that spell the log path differently share one file lock. The lock was named after the configured path before that path was resolved, so a relative and an absolute spelling of one file took two different locks and excluded nothing. The resolved path names it now. Only that spelling is covered: symbolic links, hard links, 8.3 short names, letter case and UNC versus mapped-drive spellings still count as different files, and on Windows a service and a desktop application never share the lock at all (audit da18b6fd-f031, fixed by @FreeAndNil) (319)

  • Keep the log file when a rollover cannot rename it. The failed rename was reported and the file then reopened without appending, which destroyed everything it held; a reader holding the file without FILE_SHARE_DELETE, such as a backup or antivirus agent, is enough to cause it. The file is kept and appended to now, at startup as well, and the rename is retried as the file grows, so it can exceed MaxFileSize while the rename keeps failing (audit da18b6fd-f036, fixed by @FreeAndNil) (319)

  • Stop a reconfiguration from opening a resource the outgoing appender still holds. Since 3.3.1 the new appenders were activated before the old ones were closed, so ConfigureAndWatch with a FileAppender failed to acquire the lock on its own log file. Activation waits for the swap now (reported by @urs-hart, fixed by @FreeAndNil) (321, 322)

  • Stop a repeated LoggingEvent.Fix from reopening the value cache of an already fixed event. The cache was unlocked before the fields still to fix were determined, so a redundant call reopened it even when there was nothing to do, and another thread reading a field the event never captured stored its own thread name, identity or location in it. It is now unlocked only while fields are actually being fixed (audit da18b6fd-f039, fixed by @FreeAndNil) (323)

  • Stop a disposed LogicalThreadContext.Stacks frame from bringing removed frames back. Disposing the object returned by Push rebuilt the stack from the copy taken at push time, so a frame disposed after Clear, or after the stack had been popped below its depth, reinstated the earlier frames in the current flow. Disposing now trims the stack registered in the flow and never grows it (audit da18b6fd-f044, fixed by @FreeAndNil) (323)

  • stop the configurator echoing secret configuration values. A parameter whose name reads as a secret, such as password or connectionString, is now logged as *, the connection string keeping only the keywords that name the server. This covers internal debugging, the documented first troubleshooting step, and the four setter-failure messages, which are written even without it (audit da18b6fd-f042, fixed by @FreeAndNil) (324)

  • stop UdpAppender and RemoteSyslogAppender losing an event the socket refuses to send. An event over the new MaxDatagramSize (default 65507, configurable down to 512) is truncated on a character boundary and marked …​[truncated], instead of being dropped with only the first loss reported. The limit is the IPv4 maximum and cannot be raised, an IPv6 packet would carry 20 bytes more (audit da18b6fd-f015, fixed by @FreeAndNil) (324)

  • stop LocalSyslogAppender passing a variadic argument to syslog(3), which on Apple arm64 travels differently from a fixed one, so the callee read a pointer the runtime never wrote there and logged stack bytes or crashed. Only the two fixed parameters are declared now, and the record is passed as the format string with its percent signs escaped. The logged text is unchanged (audit da18b6fd-f046, fixed by @FreeAndNil) (325)

  • bind a release to a single commit. build-release.ps1 built the binaries from the working tree but archived the local master ref, so the signed source zip need not match the signed binaries. It now refuses an unclean working tree, archives HEAD, and ships a signed .manifest recording that commit and the artifact set, which the verification scripts check against the zip archive comment and against the files present (audit da18b6fd-f025, fixed by @FreeAndNil) (330)

  • report an unexpected failure while writing to a Telnet client instead of disconnecting it. TelnetAppender read every non-fatal exception as a hung up connection, so a defect in what was written cost the connection of every client in turn. Only SocketException, IOException and ObjectDisposedException disconnect now; anything else is raised once the remaining clients have been served and is reported through the error handler. SocketHandler is protected, so a subclass calling Send sees those exceptions where it saw none (fixed by @FreeAndNil) (331)

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)