diff --git a/doc/MailTemplate.Announce.txt b/doc/MailTemplate.Announce.txt index 0de9c5c5..be253590 100644 --- a/doc/MailTemplate.Announce.txt +++ b/doc/MailTemplate.Announce.txt @@ -1,10 +1,10 @@ To: announce@apache.org, dev@logging.apache.org -Subject: [ANNOUNCE] Apache log4net 3.2.1 released +Subject: [ANNOUNCE] Apache log4net 3.3.0 released Hi, -the Apache log4net team is pleased to announce the 3.2.1 release. +the Apache log4net team is pleased to announce the 3.3.0 release. For further information (support, download, etc.) see - https://logging.apache.org/log4net/release-notes.html -- https://github.com/apache/logging-log4net/releases/tag/rel%2F3.2.1 -- https://www.nuget.org/packages/log4net/3.2.1 +- https://github.com/apache/logging-log4net/releases/tag/rel%2F3.3.0 +- https://www.nuget.org/packages/log4net/3.3.0 diff --git a/doc/MailTemplate.Result.txt b/doc/MailTemplate.Result.txt index 38e094ed..56db87a1 100644 --- a/doc/MailTemplate.Result.txt +++ b/doc/MailTemplate.Result.txt @@ -1,5 +1,5 @@ To: dev@logging.apache.org -Subject: [RESULT][VOTE] Release Apache Log4net 3.2.1 +Subject: [RESULT][VOTE] Release Apache Log4net 3.3.0 and here is my +1. @@ -9,6 +9,6 @@ I will continue the release process. Jan --------------------------------------------------------------------------------------------------- -This is a vote to release the Apache Log4net 3.2.1. +This is a vote to release the Apache Log4net 3.3.0. ... diff --git a/doc/MailTemplate.txt b/doc/MailTemplate.txt index fa50a629..5e0c0956 100644 --- a/doc/MailTemplate.txt +++ b/doc/MailTemplate.txt @@ -1,12 +1,12 @@ To: dev@logging.apache.org -Subject: [VOTE] Release Apache Log4net 3.2.1 +Subject: [VOTE] Release Apache Log4net 3.3.0 -This is a vote to release the Apache Log4net 3.2.1. +This is a vote to release the Apache Log4net 3.3.0. Website: https://logging.staged.apache.org/log4net/release-notes.html GitHub: https://github.com/apache/logging-log4net Commit: -Distribution: https://dist.apache.org/repos/dist/dev/logging/log4net/3.2.1 +Distribution: https://dist.apache.org/repos/dist/dev/logging/log4net/3.3.0 Signing key: 0x7D24496A230E29D6349A99EF583E491578F02D5D Review kit: https://logging.staged.apache.org/log4net/release-review.html diff --git a/package-lock.json b/package-lock.json index 807f504d..c75eea65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "log4net", - "version": "3.2.1", + "version": "3.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "log4net", - "version": "3.2.1", + "version": "3.3.0", "license": "Apache-2.0", "devDependencies": { "@antora/cli": "^3.2.0-alpha.9", diff --git a/package.json b/package.json index a629673e..3e5eb511 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4net", - "version": "3.2.1", + "version": "3.3.0", "description": "Log4Net is a logging framework for .NET", "repository": { "type": "git", @@ -17,4 +17,4 @@ "@antora/site-generator-default": "^3.2.0-alpha.9", "@asciidoctor/tabs": "^1.0.0-beta.6" } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index ebc397f3..8f87a734 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ pom org.apache.logging.log4net apache-log4net - 3.2.1 + 3.3.0 Apache log4net Logging framework for Microsoft .NET Framework. https://logging.apache.org/log4net diff --git a/scripts/build-preview.ps1 b/scripts/build-preview.ps1 index 3bbe40cc..2cef298a 100644 --- a/scripts/build-preview.ps1 +++ b/scripts/build-preview.ps1 @@ -1,5 +1,5 @@ param( - $Version = '3.2.1', + $Version = '3.3.0', $Preview = '1' ) 'building ...' diff --git a/scripts/build-release.ps1 b/scripts/build-release.ps1 index 1103be26..fb2b4dea 100644 --- a/scripts/build-release.ps1 +++ b/scripts/build-release.ps1 @@ -1,5 +1,5 @@ param( - $Version = '3.2.1' + $Version = '3.3.0' ) Set-StrictMode -Version Latest diff --git a/src/changelog/3.2.1/.release-notes.adoc.ftl b/src/changelog/3.3.0/.release-notes.adoc.ftl similarity index 100% rename from src/changelog/3.2.1/.release-notes.adoc.ftl rename to src/changelog/3.3.0/.release-notes.adoc.ftl diff --git a/src/changelog/3.2.1/.release.xml b/src/changelog/3.3.0/.release.xml similarity index 83% rename from src/changelog/3.2.1/.release.xml rename to src/changelog/3.3.0/.release.xml index 77b8f939..8bfd0e1a 100644 --- a/src/changelog/3.2.1/.release.xml +++ b/src/changelog/3.3.0/.release.xml @@ -2,5 +2,5 @@ \ No newline at end of file + date="2025-12-22" + version="3.3.0"/> \ No newline at end of file diff --git a/src/changelog/3.2.1/268-shorten-test-output.xml b/src/changelog/3.3.0/268-shorten-test-output.xml similarity index 100% rename from src/changelog/3.2.1/268-shorten-test-output.xml rename to src/changelog/3.3.0/268-shorten-test-output.xml diff --git a/src/changelog/3.3.0/274-newline-handling.xml b/src/changelog/3.3.0/274-newline-handling.xml new file mode 100644 index 00000000..c8e7196f --- /dev/null +++ b/src/changelog/3.3.0/274-newline-handling.xml @@ -0,0 +1,12 @@ + + + + + +https://logging.apache.org/log4net/manual/configuration/appenders/remotesyslogappender.html#newlinehandling[Newline handling] changed +(by @FreeAndNil in https://github.com/apache/logging-log4net/pull/276[#276]) + + \ No newline at end of file diff --git a/src/log4net.Tests/Appender/RemoteSyslogAppenderTest.cs b/src/log4net.Tests/Appender/RemoteSyslogAppenderTest.cs index 2a6c94fb..99b9c309 100644 --- a/src/log4net.Tests/Appender/RemoteSyslogAppenderTest.cs +++ b/src/log4net.Tests/Appender/RemoteSyslogAppenderTest.cs @@ -17,6 +17,8 @@ // #endregion +using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading; using log4net.Appender; @@ -46,21 +48,90 @@ private sealed class RemoteAppender : RemoteSyslogAppender } /// - /// Simple Test for the + /// Simple Test for the /// /// /// https://github.com/apache/logging-log4net/issues/255 /// [Test] public void RemoteSyslogTest() + { + List sentBytes = ExecuteAppend("Test message"); + const string expectedData = @"<14>TestDomain: INFO - Test message"; + Assert.That(sentBytes, Has.Count.EqualTo(1)); + Assert.That(Encoding.ASCII.GetString(sentBytes[0]), Is.EqualTo(expectedData)); + } + + /// + /// Test for the + /// with + /// + /// + /// https://github.com/apache/logging-log4net/issues/274 + /// + [Test] + public void RemoteSyslogNewLineHandlingEscapeTest() + { + List sentBytes = ExecuteAppend("Test\r\nmessage"); + // ReSharper disable once StringLiteralTypo + const string expectedData = @"<14>TestDomain: INFO - Test\r\nmessage"; + Assert.That(sentBytes, Has.Count.EqualTo(1)); + Assert.That(Encoding.ASCII.GetString(sentBytes[0]), Is.EqualTo(expectedData)); + } + + /// + /// Test for the + /// with + /// + /// + /// https://github.com/apache/logging-log4net/issues/274 + /// + [Test] + public void RemoteSyslogNewLineHandlingKeepTest() + { + List sentBytes = ExecuteAppend("Test\r\nmessage", + RemoteSyslogAppender.SyslogNewLineHandling.Keep); + // ReSharper disable once StringLiteralTypo + const string expectedData = "<14>TestDomain: INFO - Test\r\nmessage"; + Assert.That(sentBytes, Has.Count.EqualTo(1)); + Assert.That(Encoding.ASCII.GetString(sentBytes[0]), Is.EqualTo(expectedData)); + } + + /// + /// Test for the + /// with + /// + /// + /// https://github.com/apache/logging-log4net/issues/274 + /// + [Test] + public void RemoteSyslogNewLineHandlingSplitTest() + { + List sentBytes = ExecuteAppend("Test\r\nmessage", + RemoteSyslogAppender.SyslogNewLineHandling.Split); + // ReSharper disable once StringLiteralTypo + Assert.That(sentBytes, Has.Count.EqualTo(2)); + const string expectedData0 = "<14>TestDomain: INFO - Test"; + Assert.That(Encoding.ASCII.GetString(sentBytes[0]), Is.EqualTo(expectedData0)); + const string expectedData1 = "<14>TestDomain: message"; + Assert.That(Encoding.ASCII.GetString(sentBytes[1]), Is.EqualTo(expectedData1)); + } + + private static List ExecuteAppend(string message, + RemoteSyslogAppender.SyslogNewLineHandling newLineHandling = default) { System.Net.IPAddress ipAddress = new([127, 0, 0, 1]); - RemoteAppender appender = new() { RemoteAddress = ipAddress, Layout = new PatternLayout("%-5level - %message%newline") }; + RemoteAppender appender = new() + { + RemoteAddress = ipAddress, + Layout = new PatternLayout("%-5level - %message"), + NewLineHandling = newLineHandling + }; appender.ActivateOptions(); LoggingEvent loggingEvent = new(new() { Level = Level.Info, - Message = "Test message", + Message = message, LoggerName = "TestLogger", Domain = "TestDomain", }); @@ -74,9 +145,7 @@ public void RemoteSyslogTest() } appender.Close(); Assert.That(appender.Mock.ConnectedTo, Is.EqualTo((0, ipAddress, 514))); - Assert.That(appender.Mock.Sent, Has.Count.EqualTo(1)); Assert.That(appender.Mock.WasDisposed, Is.True); - const string expectedData = @"<14>TestDomain: INFO - Test message"; - Assert.That(Encoding.ASCII.GetString(appender.Mock.Sent[0].Datagram), Is.EqualTo(expectedData)); + return appender.Mock.Sent.Select(item => item.Datagram).ToList(); } } \ No newline at end of file diff --git a/src/log4net/Appender/RemoteSyslogAppender.cs b/src/log4net/Appender/RemoteSyslogAppender.cs index f9f46f76..8b5b21ea 100644 --- a/src/log4net/Appender/RemoteSyslogAppender.cs +++ b/src/log4net/Appender/RemoteSyslogAppender.cs @@ -258,6 +258,27 @@ public enum SyslogFacility Local7 = 23 } + /// + /// Options for handling newlines (\r or \n) in + /// + public enum SyslogNewLineHandling + { + /// + /// escape the newlines (\\r for \r and \\n for \n) + /// + Escape, + + /// + /// split the message at new lines + /// + Split, + + /// + /// keep newlines as is (many syslog servers can handle newlines in the message part) + /// + Keep + } + private readonly BlockingCollection _sendQueue = new(); private CancellationTokenSource? _cancellationTokenSource; private Task? _pumpTask; @@ -300,6 +321,12 @@ public RemoteSyslogAppender() /// public SyslogFacility Facility { get; set; } = SyslogFacility.User; + /// + /// NewLine handling + /// + /// The default value is . + public SyslogNewLineHandling NewLineHandling { get; set; } + /// /// Gets or sets the delegate used to create instances of . /// @@ -393,16 +420,31 @@ protected virtual void AppendMessage(string message, ref int characterIndex, Str { builder.Append(c); } - // If character is newline, break and send the current line + // If character is newline else if (c is '\r' or '\n') { - // Check the next character to handle \r\n or \n\r - if ((message.Length > characterIndex + 1) && ((message[characterIndex + 1] == '\r') || (message[characterIndex + 1] == '\n'))) + if (NewLineHandling == SyslogNewLineHandling.Escape) + { + // escape + builder.Append(c == '\r' ? "\\r" : "\\n"); + } + else if (NewLineHandling == SyslogNewLineHandling.Keep) { + // keep + builder.Append(c); + } + else if (NewLineHandling == SyslogNewLineHandling.Split) + { + // break and send the current line + // Check the next character to handle \r\n or \n\r + if ((message.Length > characterIndex + 1) + && ((message[characterIndex + 1] == '\r') || (message[characterIndex + 1] == '\n'))) + { + characterIndex++; + } characterIndex++; + break; } - characterIndex++; - break; } } } @@ -418,6 +460,11 @@ protected virtual void AppendMessage(string message, ref int characterIndex, Str public override void ActivateOptions() { base.ActivateOptions(); + if (NewLineHandling is not (SyslogNewLineHandling.Escape or SyslogNewLineHandling.Keep or SyslogNewLineHandling.Split)) + { + throw SystemInfo.CreateArgumentOutOfRangeException(nameof(NewLineHandling), NewLineHandling, + $"The NewLineHandling is not {SyslogNewLineHandling.Escape} or {SyslogNewLineHandling.Keep} or {SyslogNewLineHandling.Split}."); + } _levelMapping.ActivateOptions(); // Start the background pump _cancellationTokenSource = new(); @@ -550,7 +597,7 @@ private async Task ProcessQueueAsync(CancellationToken token) { while (!token.IsCancellationRequested) { - // Take next message or throw when cancelled + // Take next message or throw when canceled byte[] datagram = _sendQueue.Take(token); try { diff --git a/src/log4net/log4net.csproj b/src/log4net/log4net.csproj index b5cad792..f18bbf22 100644 --- a/src/log4net/log4net.csproj +++ b/src/log4net/log4net.csproj @@ -1,6 +1,6 @@ - 3.2.1 + 3.3.0 log4net Apache log4net $(Product) @@ -107,8 +107,8 @@ log4net is designed with two distinct goals in mind: speed and flexibility - - + + \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc index 141e8952..8a4557a8 100644 --- a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/remotesyslogappender.adoc @@ -60,35 +60,69 @@ You can also specify: [#newlinehandling] == Newline handling -If your log message contains multiple lines, it will be logged as multiple separate syslog entries instead of a single multiline message. +Newline handling changed in version 3.3.0. -This might lead to confusion when analyzing logs because related lines could appear interleaved with logs from other sources. +Before version 3.3.0, newline characters (`\r` or `\n`) were always treated as line breaks. +Messages containing newlines were therefore split into multiple syslog entries. -Example: +Starting with 3.3.0, newline handling is configurable through the `NewLineHandling` option. +The new default is `Escape`. + +The available modes are: + +`Escape` (default since 3.3.0):: + Newlines are replaced with the escaped representations `\r` → "\\r" and `\n` → "\\n". + The message is sent as a single syslog entry. + +`Split` (default before 3.3.0):: + The message is split at each newline into multiple syslog entries. + Combined sequences (\r\n) count as a single break. + This matches the behaviour in versions before 3.3.0. + +`Keep`:: + Newlines are preserved as-is in the message content. + Many syslog servers can process entries containing embedded newlines, but server support varies. + +=== Example: Escape + +Input: [source,csharp] ----- -try -{ - throw new InvalidTimeZoneException(); -} -catch (InvalidTimeZoneException e) -{ - logger.Error(e, "setting daylight saving time failed") -} ----- +logger.Error("A\nB\r\nC"); -Output (with intermixed messages): +Output (one syslog entry): [source,log] ----- -12/21/2024 14:07:41.508 [main] ERROR log4net.Tests - setting daylight saving time failed -Exception of type 'System.InvalidTimeZoneException' was thrown. -12/21/2024 14:07:41.511 [worker] WARN log4net.Tests - some unrelated log message - at log4net.Tests.Appender.RemoteSyslogAppenderTest.LineBreakTest() - at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) ----- +... ERROR ... - A\nB\rC + +=== Example: Split + +Input: + +[source,csharp] +logger.Error("A\nB\r\nC"); + +Output (three syslog entries, unrelated messages may interleave): + +[source,log] +... ERROR ... - A +... ERROR ... - B +... INFO ... - something unrelated +... ERROR ... - C + +=== Example: Keep + +Input: + +[source,csharp] +logger.Error("A\nB\r\nC"); + +Output (one syslog entry, with embedded newlines): + +[source,log] +... ERROR ... - A +B +C [#sizelimits] == Size limits