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
         {

Reply via email to