This is an automated email from the ASF dual-hosted git repository. freeandnil pushed a commit to branch Feature/RemoteSysLogAppender-NewLineHandling in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
commit fe2b7caaaa145033e5b960bcbd7613ab84dc8b37 Author: Jan Friedrich <[email protected]> AuthorDate: Sun Dec 7 20:55:36 2025 +0100 added NewLineHandling to RemoteSyslogAppender - fixes #274 --- .../Appender/RemoteSyslogAppenderTest.cs | 81 ++++++++++++++++++++-- src/log4net/Appender/RemoteSyslogAppender.cs | 59 ++++++++++++++-- 2 files changed, 128 insertions(+), 12 deletions(-) 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 } /// <summary> - /// Simple Test for the <see cref="RemoteSyslogAppenderTest"/> + /// Simple Test for the <see cref="RemoteSyslogAppender"/> /// </summary> /// <remarks> /// https://github.com/apache/logging-log4net/issues/255 /// </remarks> [Test] public void RemoteSyslogTest() + { + List<byte[]> 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)); + } + + /// <summary> + /// Test for the <see cref="RemoteSyslogAppender.NewLineHandling"/> + /// with <see cref="RemoteSyslogAppender.SyslogNewLineHandling.Escape"/> + /// </summary> + /// <remarks> + /// https://github.com/apache/logging-log4net/issues/274 + /// </remarks> + [Test] + public void RemoteSyslogNewLineHandlingEscapeTest() + { + List<byte[]> 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)); + } + + /// <summary> + /// Test for the <see cref="RemoteSyslogAppender.NewLineHandling"/> + /// with <see cref="RemoteSyslogAppender.SyslogNewLineHandling.Keep"/> + /// </summary> + /// <remarks> + /// https://github.com/apache/logging-log4net/issues/274 + /// </remarks> + [Test] + public void RemoteSyslogNewLineHandlingKeepTest() + { + List<byte[]> 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)); + } + + /// <summary> + /// Test for the <see cref="RemoteSyslogAppender.NewLineHandling"/> + /// with <see cref="RemoteSyslogAppender.SyslogNewLineHandling.Split"/> + /// </summary> + /// <remarks> + /// https://github.com/apache/logging-log4net/issues/274 + /// </remarks> + [Test] + public void RemoteSyslogNewLineHandlingSplitTest() + { + List<byte[]> 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<byte[]> 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 } + /// <summary> + /// Options for handling newlines (\r or \n) in <see ref="AppendMessage" /> + /// </summary> + public enum SyslogNewLineHandling + { + /// <summary> + /// escape the newlines (\\r for \r and \\n for \n) + /// </summary> + Escape, + + /// <summary> + /// split the message at new lines + /// </summary> + Split, + + /// <summary> + /// keep newlines as is (many syslog servers can handle newlines in the message part) + /// </summary> + Keep + } + private readonly BlockingCollection<byte[]> _sendQueue = new(); private CancellationTokenSource? _cancellationTokenSource; private Task? _pumpTask; @@ -300,6 +321,12 @@ public RemoteSyslogAppender() /// </remarks> public SyslogFacility Facility { get; set; } = SyslogFacility.User; + /// <summary> + /// NewLine handling + /// </summary> + /// <remarks>The default value is <see cref="SyslogNewLineHandling.Escape"/>.</remarks> + public SyslogNewLineHandling NewLineHandling { get; set; } + /// <summary> /// Gets or sets the delegate used to create instances of <see cref="IUdpConnection"/>. /// </summary> @@ -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 {
