Release notes
3.5.0
- Release date
-
2026-10-01
Added
-
TimeoutDecisiononStringMatchFilterandPropertyFilter, 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 anAcceptOnMatchallowlist followed by a deny-all it let content suppress its own record. The default staysNeutral;Acceptmakes 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 failinggpg --verifywas ignored andverify-release.ps1reported 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 withpwsh(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 holdssendQueueSizemails (500) and a logging call waits at mostenqueueTimeoutMillis(5000) for room in it. Failures are still reported to the error handler, but after the logging call has returned, andFlushnow honours its timeout (audit da18b6fd-f004, implemented by @FreeAndNil) (314) -
bound a whole send in the MailKit based
SmtpAppenderto 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.SendTimeoutMillisis 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
SmtpAppenderto 15 seconds.SmtpClient.Timeoutdefaults 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 withSendTimeoutMillis(implemented by @FreeAndNil) (314) -
move
SyslogNewLineHandlingout ofRemoteSyslogAppendertolog4net.Appender, now thatLocalSyslogAppenderuses it too. Configuration files are unaffected, they bind the value by name, but code namingRemoteSyslogAppender.SyslogNewLineHandlinghas to drop the prefix (implemented by @FreeAndNil) (315) -
The default
MatchTimeoutMillisonStringMatchFilterandPropertyFilteris 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
TelnetAppenderclients from a background thread. Clients were written to under the appender lock, so one that stopped reading blocked every thread that logs forsendTimeoutMillis, and the writes are serial, so 20 connected clients cost 20 times that on a single event. Events are queued now, bounded by the newsendQueueSize(500), and a logging call waits at mostenqueueTimeoutMillis(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,enqueueTimeoutMilliscaps logging at 20 events per second; set it to 0 to drop immediately instead of waiting (audit da18b6fd-f014, implemented by @FreeAndNil) (318) -
TelnetAppendernow listens on127.0.0.1by 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: setlistenAddressto0.0.0.0or::to restore the old behaviour. TheSocketHandler(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/v0branch oflogging-parent, which is where all Apache Logging projects now manage them. Thejs-yamloverride and the committedpackage-lock.jsonare 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 owningLogicalThreadContextStacksinstead, so nothing in log4net uses the delegate. It will be removed in version 4 (implemented by @FreeAndNil) (323) -
SmtpPickupDirAppenderno 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, whichSystemInforan on every Unix host, resolved throughPATH, so the first match there decided the answer (audit da18b6fd-f048, fixed by @FreeAndNil) (325) -
release the
FormatMessagebuffer inNativeErrorwithLocalFree, 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.MailSmtpAppender: itsTransportSecuritynow defaults toRequiredinstead ofNone, so body and credentials no longer cross the network in cleartext (CWE-319). A relay withoutSTARTTLSstops working on upgrade; settransportSecuritytoNone, orenableSsltofalse, to keep the old behaviour (audit da18b6fd-f026, implemented by @FreeAndNil) (329)
Fixed
-
Stop reporting
log4net:ERROR Exception while reading ConfigurationSettingsin a process that hosts the runtime natively, such aspowershell.exeor 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 withPlatformNotSupportedExceptionbefore 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
AdoNetAppendermessage for a connection it could not open. Hiding password-bearing keywords missedExtended Properties, which nests a whole connection string, and keywords such asAccessToken(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
KEYSfile that came with the artifacts instead of the one they download.wgetrefuses to overwrite, so a plantedKEYSstayed 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, whichgpgignores wherecommon.confsetsuse-keyboxd. Present in 3.2.0 onward, since the script was added (audit da18b6fd-f003, reported by @swebb2066, fixed by @FreeAndNil) (313) -
stop
AdoNetAppenderfrom 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 returnedtrueunconditionally, including for alossyappender, where flushing deliberately does nothing and every buffered event stays where it was (fixed by @FreeAndNil) (314) -
bound the queue
RemoteSyslogAppendersends 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 holdssendQueueSizedatagrams (500), a logging call waits at mostenqueueTimeoutMillis(5000) for room, losses are counted and reported, andFlushhonours its timeout (audit da18b6fd-f034, fixed by @FreeAndNil) (314) -
stop
RemoteSyslogAppenderfrom opening a second, unused UDP socket. It sends through the connection its background pump owns, but also inherited one fromUdpAppenderthat nothing ever used, which boundlocalPorttwice. 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
EventLogAppendercontent.ReportEventWtakes 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).WriteEntryraises 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
EventLogAppendertruncating 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 whilemessage + logName + applicationNamestays within 31736 characters, and one character beyond that the service stores nothing and reports nothing. The whole event was lost rather than shortened, andapplicationNamedefaults 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 tosyslog(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).NewLineHandlingmirrors the option of the same name onRemoteSyslogAppender, which already escaped by default; set it toKeepfor the previous behaviour (audit da18b6fd-f008, fixed by @FreeAndNil) (315) -
escape NUL characters in
OutputDebugStringAppendercontent.OutputDebugStringWtakes 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 escapeLocalSyslogAppenderalready applied is now shared between the two (audit da18b6fd-f009, fixed by @FreeAndNil) (315) -
stop one logging event destroying a whole
SmtpPickupDirAppenderbatch.File.CreateTextthrows 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\uXXXXescape (audit da18b6fd-f011, fixed by @FreeAndNil) (315) -
escape the characters
RemoteSyslogAppendercannot send instead of deleting them. RFC 3164 allows only the visible ASCII characters and space, and everything else was dropped silently, soSchönwetter 你好reached the collector asSchnwetter ` and a tab disappeared from between its neighbours. Such characters are now written as a `\uXXXXescape, which keeps the record inside the allowed range and readable (audit da18b6fd-f035, fixed by @FreeAndNil) (315) -
stop one logging event disconnecting every
TelnetAppenderclient. The default writer encoding throws on content it cannot encode, such as an unpaired surrogate, andSendreads any failure as a client that hung up. Unpaired surrogates are now written as a\uXXXXescape, as elsewhere (audit da18b6fd-f013, fixed by @FreeAndNil) (315) -
stop
AnsiColorTerminalAppenderdropping 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-requestlosing the whole event for a request that fails ASP.NET request validation. ReadingHttpRequest.Paramsvalidates 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 throughHttpRequest.Unvalidated, which keeps the content instead of dropping it (audit da18b6fd-f019, fixed by @FreeAndNil) (316) -
Compare
StringToMatchordinally inStringMatchFilterandPropertyFilter. 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 exceedMaxFileSizewhile 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
ConfigureAndWatchwith aFileAppenderfailed 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.Fixfrom 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.Stacksframe from bringing removed frames back. Disposing the object returned byPushrebuilt the stack from the copy taken at push time, so a frame disposed afterClear, 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
passwordorconnectionString, 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
UdpAppenderandRemoteSyslogAppenderlosing an event the socket refuses to send. An event over the newMaxDatagramSize(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
LocalSyslogAppenderpassing a variadic argument tosyslog(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.ps1built the binaries from the working tree but archived the localmasterref, so the signed source zip need not match the signed binaries. It now refuses an unclean working tree, archivesHEAD, and ships a signed.manifestrecording 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.
TelnetAppenderread every non-fatal exception as a hung up connection, so a defect in what was written cost the connection of every client in turn. OnlySocketException,IOExceptionandObjectDisposedExceptiondisconnect now; anything else is raised once the remaining clients have been served and is reported through the error handler.SocketHandleris protected, so a subclass callingSendsees those exceptions where it saw none (fixed by @FreeAndNil) (331)
3.4.0
- Release date
-
2026-08-21
Added
-
Add a MailKit based
SmtpAppenderin the newlog4net.Ext.Mailassembly and marklog4net.Appender.SmtpAppenderas obsolete, because Microsoft no longer recommendsSystem.Net.Mail.SmtpClientfor new development (requested by @DietzeC, implemented by @FreeAndNil in #302) (300, 302) -
pin the Maven wrapper and the Maven distribution it downloads with
wrapperSha256SumanddistributionSha256Sum.mvnwandMavenWrapperDownloaderalready 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
TransportSecurityoption to thelog4net.Ext.MailSmtpAppenderand makeEnableSsla shorthand for it.EnableSslrequires transport security, selecting implicit TLS on port 465 and mandatorySTARTTLSelsewhere, so connecting fails when the server offers no TLS instead of silently continuing in plaintext, asSystem.Net.Mail.SmtpClient.EnableSsldoes. OpportunisticSTARTTLSremains available, but only by asking for it withTransportSecurity=StartTlsWhenAvailable(audit 1231d72-f007) (310) -
add
ListenAddresstoTelnetAppender. The listening socket was bound toIPAddress.Anywith 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
AdoNetAppenderis activated withoutCommandText. In that legacy mode the renderedLayoutoutput is executed as the SQL statement, and because layouts perform no SQL quoting, logged content becomes part of the statement (CWE-89). ConfigureCommandTextwithAdoNetAppenderParameterbindings instead (audit 1231d72-f003) (310)
Changed
-
Mono is no longer required to build the
net462andnet472targets on Linux and macOS.MonoForFramework.targetsand itsFrameworkPathOverridehandling were removed in favour of theMicrosoft.NETFramework.ReferenceAssembliespackages that the .NET SDK already references implicitly. Mono is still needed to run .NET Framework assemblies: CI moved toubuntu-latest(Ubuntu 24.04) andmacos-latest(macOS 26), which no longer ship Mono, so thenet462tests 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 inlog4net.Util.SystemInfois unchanged (305) -
build the container image from
mcr.microsoft.com/dotnet/sdk:10.0-nobleinstead of installing a SDK intoubuntu:20.04withdotnet-install.sh. This moves off an Ubuntu release that left standard support in April 2025 and drops the download step, the Mono packages and theDOTNET_ROOTandPATHhandling (305) -
build and test with .NET 10: the test and integration-test projects now target
net10.0instead ofnet8.0, and a .NET 10 SDK is required because the sources use C# 14 language features. The publishedlog4netassembly still targetsnet462andnetstandard2.0, so nothing changes for consumers (305) -
bound the waits for the named mutexes used by
FileAppender.InterProcessLockand byRollingFileAppenderwhen 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 afterLockTimeoutMillis, 10000 by default, reporting through the error handler and dropping the event or skipping the roll check;Timeout.Infiniterestores the previous behaviour.AbandonedMutexExceptionis 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
RegexToMatchmatching a deadline inStringMatchFilterand the filters deriving from it,PropertyFilter,MdcFilterandNdcFilter. The pattern was matched withRegex.InfiniteMatchTimeoutwhile the appender lock was held, so a pattern that backtracks could stall everything logging through the appender on some inputs. Matching now stops afterMatchTimeoutMillis, 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.Debugis off, which is the default.OnlyOnceErrorHandleris 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.QuietandLogLog.EmitInternalMessagesremain the ways to silence internal messages (audit 1231d72-f019) (310)
Fixed
-
fix
LoggingEvent.UserNameresolving 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 bufferedFixFlags.Allevent from about 193 us to 17.5 us on the machine measured (304) -
fix the
log4net.Ext.Mailpackage failing to pack withNU5039(The readme file 'README.md' does not exist in the package), because theREADME.mditem was packed without aPackagePath(305) -
fix the release scripts continuing after a failed step:
$ErrorActionPreferencedoes not apply to native commands, so a failingdotnet,git,zip,gpgormvnwwent unnoticed and the artifacts were packaged, signed and tagged anyway. Bothscripts/build-preview.ps1andscripts/build-release.ps1now set$PSNativeCommandUseErrorActionPreference. Also fixscripts/build-preview.ps1outside Windows, where the artifact paths handed togpgwere built with backslashes, which are not path separators on Linux or macOS (305) -
Make log4net usable from a
PublishAotbuild, whereLogManager.GetLogger()used to throwPlatformNotSupportedExceptionfromAssembly.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 toBufferSizeunrelated events, which an attacker could trigger repeatedly with content the provider refuses, such asU+0000on 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
LocalSyslogAppenderrecords. 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, becausesyslog(3)encodes them and newlines are needed for multi-line exception output (audit 1231d72-f008) (310) -
make
TextWriterAppenderand the appenders deriving from it, includingFileAppenderandRollingFileAppender, flush under the appender lock.Flushsynchronized on a private object whileAppendruns under the lock taken byDoAppend, so a flush could run concurrently with a write to the sameQuietTextWriter, which is not thread safe, and interleave or lose output.Flushalso returnedtruewhatever happened and let a failure from the underlying writer escape to its caller; it now reports through theErrorHandlerand returnsfalse(audit 1231d72-f020) (310) -
stop
AdoNetAppenderfrom 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 embedPassword=…(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
RemoteSyslogAppenderIdentitythat 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
LocalSyslogAppenderidentity.openlogkeeps the pointer it is given rather than a copy of the string, and registers it for the process rather than for an appender. EachActivateOptionsallocated a new buffer and forgot the previous one, leaking it (CWE-401), whileOnClosefreed a buffer that another instance may still have been logging through. The handle is now shared, replaced under a lock onceopenlogpoints 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.
TelnetAppenderwrites to its clients while the appender lock is held and set noSocket.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 afterSendTimeoutMillis(5000 by default) and the client is disconnected; 0 restores the previous unbounded behavior (audit 1231d72-f001) (310) -
stop
LoggingEvent.UserNamereporting 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.ps1reported a SHA-512 mismatch with-ErrorAction Continueand never checked the exit code ofgpg, so it extracted the archive and exited 0 for a tampered artifact or a broken signature. Both scripts looped over whichever.ascfiles 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.sha512and 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-broadcastworkflow. Its push and pull request triggers had been commented out, leaving it dispatched by hand only, while it still rannpx 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 nopermissionsblock (CWE-1357) (audit 1231d72-f011) (310)
3.3.2
- Release date
-
2026-06-25
Fixed
-
fix dropped log messages during concurrent
GetLoggercalls (reported by @omtslug, fixed by @gdziadkiewicz in #294) (292, 294) -
fix race condition in
AppenderSkeleton.AddFilterandClearFiltersunder concurrent logging (CWE-362) (f57d7b3-001, 298) -
fix unhandled exception in
AppenderSkeletonfinalizer that could terminate the process whenOnClose()throws (CWE-755) (f57d7b3-003, 298) -
fix mutex leak in
InterProcessLock.AcquireLockwhen 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.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()onSendAsync()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.0.4
- Release date
-
2025-02-21
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)
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)
3.0.0
- Release date
-
2024-09-15
Breaking changes
|
Starting with 3.0.0 we only support the following target frameworks
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
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)
2.x
Changed
-
release notes for older releases can be found here (2.x-and-older)