2323using log4net . Util ;
2424using log4net . Layout ;
2525using System . Text ;
26+ using System . Net . Sockets ;
27+ using System . Threading . Tasks ;
28+ using System . Threading ;
29+ using System . Collections . Concurrent ;
2630
2731namespace log4net . Appender ;
2832
@@ -255,6 +259,10 @@ public enum SyslogFacility
255259 Local7 = 23
256260 }
257261
262+ private readonly BlockingCollection < byte [ ] > _sendQueue = new ( ) ;
263+ private CancellationTokenSource ? _cts ;
264+ private Task ? _pumpTask ;
265+
258266 /// <summary>
259267 /// Initializes a new instance of the <see cref="RemoteSyslogAppender" /> class.
260268 /// </summary>
@@ -367,7 +375,8 @@ protected override void Append(LoggingEvent loggingEvent)
367375 // Grab as a byte array
368376 byte [ ] buffer = Encoding . GetBytes ( builder . ToString ( ) ) ;
369377
370- Client . SendAsync ( buffer , buffer . Length , RemoteEndPoint ) . Wait ( ) ;
378+ //Client.SendAsync(buffer, buffer.Length, RemoteEndPoint).Wait();
379+ _sendQueue . Add ( buffer ) ;
371380 }
372381 }
373382 catch ( Exception e ) when ( ! e . IsFatal ( ) )
@@ -424,8 +433,11 @@ public override void ActivateOptions()
424433 {
425434 base . ActivateOptions ( ) ;
426435 _levelMapping . ActivateOptions ( ) ;
436+ // Start the background pump
437+ _cts = new CancellationTokenSource ( ) ;
438+ _pumpTask = Task . Run ( ( ) => ProcessQueueAsync ( _cts . Token ) , CancellationToken . None ) ;
427439 }
428-
440+
429441 /// <summary>
430442 /// Translates a log4net level to a syslog severity.
431443 /// </summary>
@@ -531,4 +543,47 @@ public class LevelSeverity : LevelMappingEntry
531543 /// </remarks>
532544 public SyslogSeverity Severity { get ; set ; }
533545 }
546+
547+ protected override void OnClose ( )
548+ {
549+ // Signal shutdown and wait for the pump to drain
550+ _cts ? . Cancel ( ) ;
551+ _pumpTask ? . Wait ( TimeSpan . FromSeconds ( 5 ) ) ; // or your own timeout
552+ base . OnClose ( ) ;
553+ }
554+
555+ private async Task ProcessQueueAsync ( CancellationToken token )
556+ {
557+ // We create our own UdpClient here, so that client lifetime is tied to this task
558+ using ( var udp = new UdpClient ( ) )
559+ {
560+ udp . Connect ( RemoteAddress ? . ToString ( ) , RemotePort ) ;
561+
562+ try
563+ {
564+ while ( ! token . IsCancellationRequested )
565+ {
566+ // Take next message or throw when cancelled
567+ byte [ ] datagram = _sendQueue . Take ( token ) ;
568+ try
569+ {
570+ await udp . SendAsync ( datagram , datagram . Length ) ;
571+ }
572+ catch ( Exception ex ) when ( ! ex . IsFatal ( ) )
573+ {
574+ ErrorHandler . Error ( "RemoteSyslogAppender: send failed" , ex , ErrorCode . WriteFailure ) ;
575+ }
576+ }
577+ }
578+ catch ( OperationCanceledException )
579+ {
580+ // Clean shutdown: drain remaining items if desired
581+ while ( _sendQueue . TryTake ( out var leftover ) )
582+ {
583+ try { await udp . SendAsync ( leftover , leftover . Length ) ; }
584+ catch { /* ignore */ }
585+ }
586+ }
587+ }
588+ }
534589}
0 commit comments