Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FilteringTargetWrapper: Add support for batch writing + PostFilteringTargetWrapper: performance optimizations #3405

Merged
merged 9 commits into from May 20, 2019
29 changes: 29 additions & 0 deletions src/NLog/Config/LoggingConfigurationParser.cs
Expand Up @@ -1021,6 +1021,11 @@ private void SetPropertyFromElement(object o, ILoggingConfigurationElement eleme
return;
}

if (SetFilterFromElement(o, propInfo, element))
{
return;
}

SetItemFromElement(o, propInfo, element);
}

Expand Down Expand Up @@ -1095,6 +1100,30 @@ private Layout TryCreateLayoutInstance(ILoggingConfigurationElement element, Typ
return _configurationItemFactory.Layouts.CreateInstance(ExpandSimpleVariables(layoutTypeName));
}

private bool SetFilterFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement layoutElement)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
// Check if it is a Filter
if (!typeof(Filter).IsAssignableFrom(propInfo.PropertyType))
return false;

// Check if the 'type' attribute has been specified
string filterTypeName = GetConfigItemTypeAttribute(layoutElement);
if (filterTypeName == null)
return false;

Filter filter = _configurationItemFactory.Filters.CreateInstance(ExpandSimpleVariables(filterTypeName));
// and is a Filter and 'type' attribute has been specified
if (filter != null)
{
ConfigureObjectFromAttributes(filter, layoutElement, true);
ConfigureObjectFromElement(filter, layoutElement);
propInfo.SetValue(o, filter, null);
return true;
}

return false;
}

private void SetItemFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement element)
{
object item = propInfo.GetValue(o, null);
Expand Down
4 changes: 3 additions & 1 deletion src/NLog/Filters/ConditionBasedFilter.cs
Expand Up @@ -55,6 +55,8 @@ public class ConditionBasedFilter : Filter
[RequiredParameter]
public ConditionExpression Condition { get; set; }

internal FilterResult DefaultFilterResult { get; set; } = FilterResult.Neutral;

/// <summary>
/// Checks whether log event should be logged or not.
/// </summary>
Expand All @@ -72,7 +74,7 @@ protected override FilterResult Check(LogEventInfo logEvent)
return Action;
}

return FilterResult.Neutral;
return DefaultFilterResult;
}
}
}
92 changes: 81 additions & 11 deletions src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs
Expand Up @@ -33,11 +33,11 @@

namespace NLog.Targets.Wrappers
{
using System;
using Common;
using Conditions;
using Config;
using Internal;
using System.Collections.Generic;
using NLog.Common;
using NLog.Conditions;
using NLog.Config;
using NLog.Filters;

/// <summary>
/// Filters log entries based on a condition.
Expand All @@ -59,8 +59,6 @@ namespace NLog.Targets.Wrappers
[Target("FilteringWrapper", IsWrapper = true)]
public class FilteringTargetWrapper : WrapperTargetBase
{
private static readonly object boxedBooleanTrue = true;

/// <summary>
/// Initializes a new instance of the <see cref="FilteringTargetWrapper" /> class.
/// </summary>
Expand Down Expand Up @@ -90,16 +88,32 @@ public FilteringTargetWrapper(Target wrappedTarget, ConditionExpression conditio
{
WrappedTarget = wrappedTarget;
Condition = condition;
OptimizeBufferReuse = GetType() == typeof(FilteringTargetWrapper); // Class not sealed, reduce breaking changes
}

/// <summary>
/// Gets or sets the condition expression. Log events who meet this condition will be forwarded
/// to the wrapped target.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public ConditionExpression Condition { get => (Filter as ConditionBasedFilter)?.Condition; set => Filter = value != null ? new ConditionBasedFilter() { Condition = value, DefaultFilterResult = FilterResult.Ignore } : null; }

/// <summary>
/// Gets or sets the filter. Log events who evaluates to <see cref="FilterResult.Ignore"/> will be discarded
/// </summary>
/// <docgen category='Filtering Options' order='10' />
[RequiredParameter]
public ConditionExpression Condition { get; set; }
public Filter Filter { get; set; }

/// <inheritdoc/>
protected override void InitializeTarget()
{
base.InitializeTarget();

if (!OptimizeBufferReuse && WrappedTarget != null && WrappedTarget.OptimizeBufferReuse)
{
OptimizeBufferReuse = GetType() == typeof(FilteringTargetWrapper); // Class not sealed, reduce breaking changes
}
}

/// <summary>
/// Checks the condition against the passed log event.
Expand All @@ -109,8 +123,7 @@ public FilteringTargetWrapper(Target wrappedTarget, ConditionExpression conditio
/// <param name="logEvent">Log event.</param>
protected override void Write(AsyncLogEventInfo logEvent)
{
object v = Condition.Evaluate(logEvent.LogEvent);
if (boxedBooleanTrue.Equals(v))
if (FilterIncludeLogEvent(logEvent.LogEvent))
{
WrappedTarget.WriteAsyncLogEvent(logEvent);
}
Expand All @@ -119,5 +132,62 @@ protected override void Write(AsyncLogEventInfo logEvent)
logEvent.Continuation(null);
}
}

/// <inheritdoc/>
protected override void Write(IList<AsyncLogEventInfo> logEvents)
{
bool hasIgnoredLogEvents = false;
IList<AsyncLogEventInfo> filterLogEvents = null;
AsyncLogEventInfo filterLogEvent = default(AsyncLogEventInfo);
for (int i = 0; i < logEvents.Count; ++i)
{
if (FilterIncludeLogEvent(logEvents[i].LogEvent))
{
if (hasIgnoredLogEvents && filterLogEvents == null)
{
if (filterLogEvent.LogEvent != null)
{
filterLogEvents = new List<AsyncLogEventInfo>();
filterLogEvents.Add(filterLogEvent);
filterLogEvent = default(AsyncLogEventInfo);
}
else
{
filterLogEvent = logEvents[i];
}
}

if (filterLogEvents != null)
filterLogEvents.Add(logEvents[i]);
}
else
{
if (!hasIgnoredLogEvents && i > 0)
{
filterLogEvents = new List<AsyncLogEventInfo>();
for (int j = 0; j < i; ++j)
filterLogEvents.Add(logEvents[j]);
}
hasIgnoredLogEvents = true;
logEvents[i].Continuation(null);
}
}

if (!hasIgnoredLogEvents)
WrappedTarget.WriteAsyncLogEvents(logEvents);
else if (filterLogEvents != null)
WrappedTarget.WriteAsyncLogEvents(filterLogEvents);
else if (filterLogEvent.LogEvent != null)
WrappedTarget.WriteAsyncLogEvent(filterLogEvent);
}

private bool FilterIncludeLogEvent(LogEventInfo logEvent)
{
var filterResult = Filter.GetFilterResult(logEvent);
if (filterResult == FilterResult.Ignore || filterResult == FilterResult.IgnoreFinal)
return false;
else
return true;
}
}
}
83 changes: 51 additions & 32 deletions src/NLog/Targets/Wrappers/PostFilteringTargetWrapper.cs
Expand Up @@ -35,10 +35,10 @@ namespace NLog.Targets.Wrappers
{
using System;
using System.Collections.Generic;
using Common;
using Conditions;
using Config;
using Internal;
using NLog.Common;
using NLog.Conditions;
using NLog.Config;
using NLog.Internal;

/// <summary>
/// Filters buffered log entries based on a set of conditions that are evaluated on a group of events.
Expand Down Expand Up @@ -75,18 +75,17 @@ public class PostFilteringTargetWrapper : WrapperTargetBase
/// <summary>
/// Initializes a new instance of the <see cref="PostFilteringTargetWrapper" /> class.
/// </summary>
public PostFilteringTargetWrapper() : this(null)
public PostFilteringTargetWrapper()
: this(null)
{
Rules = new List<FilteringRule>();
}

/// <summary>
/// Initializes a new instance of the <see cref="PostFilteringTargetWrapper" /> class.
/// </summary>
public PostFilteringTargetWrapper(Target wrappedTarget)
: this(null, wrappedTarget)
{
Rules = new List<FilteringRule>();
WrappedTarget = wrappedTarget;
}

/// <summary>
Expand All @@ -95,9 +94,10 @@ public PostFilteringTargetWrapper(Target wrappedTarget)
/// <param name="name">Name of the target.</param>
/// <param name="wrappedTarget">The wrapped target.</param>
public PostFilteringTargetWrapper(string name, Target wrappedTarget)
: this(wrappedTarget)
{
Name = name;
WrappedTarget = wrappedTarget;
Rules = new List<FilteringRule>();
}

/// <summary>
Expand All @@ -115,6 +115,23 @@ public PostFilteringTargetWrapper(string name, Target wrappedTarget)
[ArrayParameter(typeof(FilteringRule), "when")]
public IList<FilteringRule> Rules { get; private set; }

/// <inheritdoc/>
protected override void InitializeTarget()
{
base.InitializeTarget();

if (!OptimizeBufferReuse && WrappedTarget != null && WrappedTarget.OptimizeBufferReuse)
{
OptimizeBufferReuse = GetType() == typeof(PostFilteringTargetWrapper); // Class not sealed, reduce breaking changes
}
}

/// <inheritdoc/>
protected override void Write(AsyncLogEventInfo logEvent)
{
Write((IList<AsyncLogEventInfo>)new[] { logEvent }); // Single LogEvent should also work
}

/// <summary>
/// NOTE! Obsolete, instead override Write(IList{AsyncLogEventInfo} logEvents)
///
Expand All @@ -138,22 +155,17 @@ protected override void Write(AsyncLogEventInfo[] logEvents)
/// <param name="logEvents">Array of log events to be post-filtered.</param>
protected override void Write(IList<AsyncLogEventInfo> logEvents)
{


InternalLogger.Trace("PostFilteringWrapper(Name={0}): Running on {1} events", Name, logEvents.Count);

var resultFilter = EvaluateAllRules(logEvents) ?? DefaultFilter;

if (resultFilter == null)
{
WrappedTarget.WriteAsyncLogEvents(logEvents);
}
else
{
InternalLogger.Trace("PostFilteringWrapper(Name={0}): Filter to apply: {1}", Name, resultFilter);

var resultBuffer = ApplyFilter(logEvents, resultFilter);

InternalLogger.Trace("PostFilteringWrapper(Name={0}): After filtering: {1} events.", Name, resultBuffer.Count);
if (resultBuffer.Count > 0)
{
Expand All @@ -169,25 +181,38 @@ protected override void Write(IList<AsyncLogEventInfo> logEvents)
/// <param name="logEvents"></param>
/// <param name="resultFilter"></param>
/// <returns></returns>
private static List<AsyncLogEventInfo> ApplyFilter(IList<AsyncLogEventInfo> logEvents, ConditionExpression resultFilter)
private static IList<AsyncLogEventInfo> ApplyFilter(IList<AsyncLogEventInfo> logEvents, ConditionExpression resultFilter)
304NotModified marked this conversation as resolved.
Show resolved Hide resolved
{
var resultBuffer = new List<AsyncLogEventInfo>();

bool hasIgnoredLogEvents = false;
IList<AsyncLogEventInfo> resultBuffer = null;
for (int i = 0; i < logEvents.Count; ++i)
{
object v = resultFilter.Evaluate(logEvents[i].LogEvent);
if (boxedTrue.Equals(v))
{
resultBuffer.Add(logEvents[i]);
if (hasIgnoredLogEvents && resultBuffer == null)
{
resultBuffer = new List<AsyncLogEventInfo>();
}

if (resultBuffer != null)
resultBuffer.Add(logEvents[i]);
}
else
{
if (!hasIgnoredLogEvents && i > 0)
{
resultBuffer = new List<AsyncLogEventInfo>();
for (int j = 0; j < i; ++j)
resultBuffer.Add(logEvents[j]);
}
hasIgnoredLogEvents = true;
// anything not passed down will be notified about successful completion
logEvents[i].Continuation(null);
}
}

return resultBuffer;
return resultBuffer ?? (hasIgnoredLogEvents ? ArrayHelper.Empty<AsyncLogEventInfo>() : logEvents);
}

/// <summary>
Expand All @@ -197,30 +222,24 @@ private static List<AsyncLogEventInfo> ApplyFilter(IList<AsyncLogEventInfo> logE
/// <returns></returns>
private ConditionExpression EvaluateAllRules(IList<AsyncLogEventInfo> logEvents)
{
ConditionExpression resultFilter = null;

if (Rules.Count == 0)
return null;

for (int i = 0; i < logEvents.Count; ++i)
{
foreach (FilteringRule rule in Rules)
for (int j = 0; j < Rules.Count; ++j)
{
var rule = Rules[j];
object v = rule.Exists.Evaluate(logEvents[i].LogEvent);

if (boxedTrue.Equals(v))
{
InternalLogger.Trace("PostFilteringWrapper(Name={0}): Rule matched: {1}", Name, rule.Exists);

resultFilter = rule.Filter;
break;
return rule.Filter;
}
}

if (resultFilter != null)
{
break;
}
}

return resultFilter;
return null;
}
}
}
4 changes: 1 addition & 3 deletions tests/NLog.UnitTests/Targets/TargetTests.cs
Expand Up @@ -84,8 +84,6 @@ public void TargetContructorWithNameTest()

var args = new List<object> { fileTarget };



//default ctor
var defaultConstructedTarget = (WrapperTargetBase)Activator.CreateInstance(targetType);
defaultConstructedTarget.Name = name;
Expand All @@ -94,7 +92,7 @@ public void TargetContructorWithNameTest()
//specials cases
if (targetType == typeof(FilteringTargetWrapper))
{
var cond = new ConditionLoggerNameExpression();
ConditionLoggerNameExpression cond = null;
args.Add(cond);
var target = (FilteringTargetWrapper) defaultConstructedTarget;
target.Condition = cond;
Expand Down