Skip to content

Commit

Permalink
Add LogEventDropped handler to the NetworkTarget (#5101)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowDancer committed Nov 17, 2022
1 parent a293624 commit b1bf791
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/NLog/Internal/NetworkSenders/INetworkSenderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ internal interface INetworkSenderFactory
/// <returns>
/// A newly created network sender.
/// </returns>
NetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime);
QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime);
}
}
2 changes: 1 addition & 1 deletion src/NLog/Internal/NetworkSenders/NetworkSenderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class NetworkSenderFactory : INetworkSenderFactory
public static readonly INetworkSenderFactory Default = new NetworkSenderFactory();

/// <inheritdoc/>
public NetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime)
public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, System.Security.Authentication.SslProtocols sslProtocols, TimeSpan keepAliveTime)
{
if (url.StartsWith("tcp://", StringComparison.OrdinalIgnoreCase))
{
Expand Down
7 changes: 7 additions & 0 deletions src/NLog/Internal/NetworkSenders/QueuedNetworkSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ protected QueuedNetworkSender(string url)
public int MaxQueueSize { get; set; }

public NetworkTargetQueueOverflowAction OnQueueOverflow { get; set; }
public event EventHandler<NetworkLogEventDroppedEventArgs> LogEventDropped;

protected override void DoSend(byte[] bytes, int offset, int length, AsyncContinuation asyncContinuation)
{
Expand All @@ -94,6 +95,7 @@ protected override void DoSend(byte[] bytes, int offset, int length, AsyncContin
{
case NetworkTargetQueueOverflowAction.Discard:
InternalLogger.Debug("NetworkQueue - Discarding single item, because queue is full");
OnLogEventDropped(this, NetworkLogEventDroppedEventArgs.QueueOverflow);
var dequeued = _pendingRequests.Dequeue();
dequeued.AsyncContinuation?.Invoke(null);
break;
Expand Down Expand Up @@ -299,5 +301,10 @@ private void SignalSocketFailedForPendingRequests(Exception pendingException)
eventArgs.AsyncContinuation?.Invoke(pendingException);
}
}

private void OnLogEventDropped(object sender, NetworkLogEventDroppedEventArgs logEventDroppedEventArgs)
{
LogEventDropped?.Invoke(this, logEventDroppedEventArgs);
}
}
}
71 changes: 71 additions & 0 deletions src/NLog/Targets/NetworkLogEventDroppedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Copyright (c) 2004-2021 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//

using System;

namespace NLog.Targets
{
/// <summary>
/// Arguments for <see cref="NetworkTarget.LogEventDropped"/> events.
/// </summary>
public class NetworkLogEventDroppedEventArgs : EventArgs
{
/// <summary>
/// Provides a value to use with events that do not have event data.
/// </summary>
internal static readonly NetworkLogEventDroppedEventArgs PayloadSizeOverflow = new NetworkLogEventDroppedEventArgs(NetworkLogEventDroppedReason.PayloadSizeOverflow);

/// <summary>
/// Provides a value to use with events that do not have event data.
/// </summary>
internal static readonly NetworkLogEventDroppedEventArgs ConnectionOverflow = new NetworkLogEventDroppedEventArgs(NetworkLogEventDroppedReason.ConnectionOverflow);

/// <summary>
/// Provides a value to use with events that do not have event data.
/// </summary>
internal static readonly NetworkLogEventDroppedEventArgs QueueOverflow = new NetworkLogEventDroppedEventArgs(NetworkLogEventDroppedReason.QueueOverflow);

/// <summary>
/// Creates new instance of NetworkTargetLogEventDroppedEventArgs
/// </summary>
public NetworkLogEventDroppedEventArgs(NetworkLogEventDroppedReason reason)
{
Reason = reason;
}

/// <summary>
/// The reason why log was dropped
/// </summary>
public NetworkLogEventDroppedReason Reason { get; }
}
}
54 changes: 54 additions & 0 deletions src/NLog/Targets/NetworkLogEventDroppedReason.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright (c) 2004-2021 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//

namespace NLog.Targets
{
/// <summary>
/// The reason why log event was dropped by <see cref="NetworkTarget"/>
/// </summary>
public enum NetworkLogEventDroppedReason
{
/// <summary>
/// Discarded LogEvent because message is bigger than <see cref="NetworkTarget.MaxMessageSize"/>
/// </summary>
PayloadSizeOverflow,
/// <summary>
/// Discarded LogEvent because message queue was bigger than <see cref="NetworkTarget.MaxQueueSize"/>
/// </summary>
QueueOverflow,
/// <summary>
/// Discarded LogEvent because attempted to open more than <see cref="NetworkTarget.MaxConnections"/> connections
/// </summary>
ConnectionOverflow
}
}
17 changes: 17 additions & 0 deletions src/NLog/Targets/NetworkTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace NLog.Targets
using NLog.Internal;
using NLog.Internal.NetworkSenders;
using NLog.Layouts;
using NLog.Targets.Wrappers;

/// <summary>
/// Sends log messages over the network.
Expand Down Expand Up @@ -186,6 +187,12 @@ public LineEndingMode LineEnding
/// <docgen category='Connection Options' order='10' />
public NetworkTargetQueueOverflowAction OnQueueOverflow { get; set; } = NetworkTargetQueueOverflowAction.Discard;

/// <summary>
/// Raise event when Target cannot store LogEvent and <see cref="OnQueueOverflow"/> equals <see cref="NetworkTargetOverflowAction.Discard"/>.
/// Event arg contains lost LogEvents
/// </summary>
public event EventHandler<NetworkLogEventDroppedEventArgs> LogEventDropped;

/// <summary>
/// Gets or sets the size of the connection cache (number of connections which are kept alive). Requires <see cref="KeepConnection"/> = true
/// </summary>
Expand Down Expand Up @@ -360,6 +367,7 @@ private void WriteBytesToNewNetworkSender(string address, byte[] bytes, AsyncLog
{
case NetworkTargetConnectionsOverflowAction.Discard:
InternalLogger.Debug("{0}: Discarding message, because too many open connections.", this);
OnLogEventDropped(this, NetworkLogEventDroppedEventArgs.ConnectionOverflow);
logEvent.Continuation(null);
return;

Expand Down Expand Up @@ -418,6 +426,11 @@ private void WriteBytesToNewNetworkSender(string address, byte[] bytes, AsyncLog
});
}

private void OnLogEventDropped(object sender, NetworkLogEventDroppedEventArgs logEventDroppedEventArgs)
{
LogEventDropped?.Invoke(this, logEventDroppedEventArgs);
}

/// <summary>
/// Try to remove.
/// </summary>
Expand Down Expand Up @@ -567,6 +580,8 @@ private NetworkSender CreateNetworkSender(string address)
{
var sender = SenderFactory.Create(address, MaxQueueSize, OnQueueOverflow, MaxMessageSize, SslProtocols, TimeSpan.FromSeconds(KeepAliveTimeSeconds));
sender.Initialize();
sender.LogEventDropped += OnLogEventDropped;

return sender;
}

Expand Down Expand Up @@ -600,13 +615,15 @@ private void WriteBytesToNetworkSender(NetworkSender sender, byte[] buffer, Asyn
if (OnOverflow == NetworkTargetOverflowAction.Discard)
{
InternalLogger.Debug("{0}: Discarded LogEvent because MessageSize={1} is above MaxMessageSize={2}", this, messageSize, MaxMessageSize);
OnLogEventDropped(this, NetworkLogEventDroppedEventArgs.PayloadSizeOverflow);
continuation(null);
return;
}

if (OnOverflow == NetworkTargetOverflowAction.Error)
{
InternalLogger.Debug("{0}: Discarded LogEvent because MessageSize={1} is above MaxMessageSize={2}", this, messageSize, MaxMessageSize);
OnLogEventDropped(this, NetworkLogEventDroppedEventArgs.PayloadSizeOverflow);
continuation(new InvalidOperationException($"NetworkTarget: Discarded LogEvent because MessageSize={messageSize} is above MaxMessageSize={MaxMessageSize}"));
return;
}
Expand Down
16 changes: 15 additions & 1 deletion tests/NLog.UnitTests/Targets/NetworkTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ public void NetworkTargetMultipleConnectionsWithMessageDiscardTest()
target.OnOverflow = NetworkTargetOverflowAction.Discard;
target.Initialize(null);

int droppedLogs = 0;
target.LogEventDropped += (sender, args) => droppedLogs++;

var exceptions = new List<Exception>();
var mre = new ManualResetEvent(false);
int remaining = 3;
Expand Down Expand Up @@ -399,6 +402,7 @@ public void NetworkTargetMultipleConnectionsWithMessageDiscardTest()
Assert.True(result.IndexOf("2: send 0 5") != -1);
Assert.True(result.IndexOf("1: close") != -1);
Assert.True(result.IndexOf("2: close") != -1);
Assert.Equal(1, droppedLogs);
}

[Fact]
Expand All @@ -417,6 +421,9 @@ public void NetworkTargetMultipleConnectionsWithMessageErrorTest()
target.OnOverflow = NetworkTargetOverflowAction.Error;
target.Initialize(null);

int droppedLogs = 0;
target.LogEventDropped += (sender, args) => droppedLogs++;

var exceptions = new List<Exception>();
var mre = new ManualResetEvent(false);
int remaining = 3;
Expand Down Expand Up @@ -451,6 +458,8 @@ public void NetworkTargetMultipleConnectionsWithMessageErrorTest()
Assert.True(result.IndexOf("2: connect tcp://logger2.company.lan/") != -1);
Assert.True(result.IndexOf("2: send 0 5") != -1);
Assert.True(result.IndexOf("2: close") != -1);

Assert.Equal(1, droppedLogs);
}

[Fact]
Expand Down Expand Up @@ -813,6 +822,9 @@ public void NetworkTargetQueueDiscardTest()
target.KeepConnection = true;
target.Initialize(null);

int discardEventCalls = 0;
target.LogEventDropped += (sender, args) => discardEventCalls++;

int pendingWrites = 1;
var exceptions = new List<Exception>();
var mre = new ManualResetEvent(false);
Expand Down Expand Up @@ -853,6 +865,8 @@ public void NetworkTargetQueueDiscardTest()
{
Assert.True(ex == null, $"Network Write Exception: {ex?.ToString()}");
}

Assert.True(discardEventCalls >= 5, $"LogDropped not called: {discardEventCalls}");
}

[Fact]
Expand Down Expand Up @@ -1200,7 +1214,7 @@ internal class MyQueudSenderFactory : INetworkSenderFactory
internal StringWriter Log = new StringWriter();
private int _idCounter;

public NetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, SslProtocols sslProtocols, TimeSpan keepAliveTime)
public QueuedNetworkSender Create(string url, int maxQueueSize, NetworkTargetQueueOverflowAction onQueueOverflow, int maxMessageSize, SslProtocols sslProtocols, TimeSpan keepAliveTime)
{
var sender = new MyQueudNetworkSender(url, ++_idCounter, Log, this) { MaxQueueSize = maxQueueSize, OnQueueOverflow = onQueueOverflow };
Senders.Add(sender);
Expand Down

0 comments on commit b1bf791

Please sign in to comment.