Skip to content

Commit

Permalink
FilteringTargetWrapper: Add support for batch writing + PostFiltering…
Browse files Browse the repository at this point in the history
…TargetWrapper: performance optimizations (#3405)

* FilteringTargetWrapper - Add support for batch writing and use of whenRepeated as Filter
  • Loading branch information
snakefoot authored and 304NotModified committed May 20, 2019
1 parent 3d07bd9 commit dfd3845
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 73 deletions.
59 changes: 50 additions & 9 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 @@ -1065,16 +1070,29 @@ private object ParseArrayItemFromElement(Type elementType, ILoggingConfiguration
return arrayItem;
}

private bool SetLayoutFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement layoutElement)
private bool SetLayoutFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement element)
{
Layout layout = TryCreateLayoutInstance(layoutElement, propInfo.PropertyType);
var layout = TryCreateLayoutInstance(element, propInfo.PropertyType);

// and is a Layout and 'type' attribute has been specified
if (layout != null)
{
ConfigureObjectFromAttributes(layout, layoutElement, true);
ConfigureObjectFromElement(layout, layoutElement);
propInfo.SetValue(o, layout, null);
SetItemOnProperty(o, propInfo, element, layout);
return true;
}

return false;
}

private bool SetFilterFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement element)
{
var type = propInfo.PropertyType;

Filter filter = TryCreateFilterInstance(element, type);
// and is a Filter and 'type' attribute has been specified
if (filter != null)
{
SetItemOnProperty(o, propInfo, element, filter);
return true;
}

Expand All @@ -1083,16 +1101,39 @@ private bool SetLayoutFromElement(object o, PropertyInfo propInfo, ILoggingConfi

private Layout TryCreateLayoutInstance(ILoggingConfigurationElement element, Type type)
{
// Check if it is a Layout
if (!typeof(Layout).IsAssignableFrom(type))
return TryCreateInstance(element, type, _configurationItemFactory.Layouts);
}

private Filter TryCreateFilterInstance(ILoggingConfigurationElement element, Type type)
{
return TryCreateInstance(element, type, _configurationItemFactory.Filters);
}

private T TryCreateInstance<T>(ILoggingConfigurationElement element, Type type, INamedItemFactory<T, Type> factory)
where T : class
{
// Check if correct type
if (!IsAssignableFrom<T>(type))
return null;

// Check if the 'type' attribute has been specified
string layoutTypeName = GetConfigItemTypeAttribute(element);
if (layoutTypeName == null)
return null;

return _configurationItemFactory.Layouts.CreateInstance(ExpandSimpleVariables(layoutTypeName));
return factory.CreateInstance(ExpandSimpleVariables(layoutTypeName));
}

private static bool IsAssignableFrom<T>(Type type)
{
return typeof(T).IsAssignableFrom(type);
}

private void SetItemOnProperty(object o, PropertyInfo propInfo, ILoggingConfigurationElement element, object properyValue)
{
ConfigureObjectFromAttributes(properyValue, element, true);
ConfigureObjectFromElement(properyValue, element);
propInfo.SetValue(o, properyValue, null);
}

private void SetItemFromElement(object o, PropertyInfo propInfo, ILoggingConfigurationElement element)
Expand Down Expand Up @@ -1223,7 +1264,7 @@ private static string StripOptionalNamespacePrefix(string attributeValue)

return attributeValue.Substring(p + 1);
}

private static string GetName(Target target)
{
return string.IsNullOrEmpty(target.Name) ? target.GetType().Name : target.Name;
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;
}
}
}
77 changes: 77 additions & 0 deletions src/NLog/Internal/CollectionExtensions.cs
@@ -0,0 +1,77 @@
//
// Copyright (c) 2004-2019 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;
using System.Collections.Generic;
using JetBrains.Annotations;

namespace NLog.Internal
{
internal static class CollectionExtensions
{
/// <summary>
/// Memory optimized filtering
/// </summary>
/// <remarks>Passing state too avoid delegate capture and memory-allocations.</remarks>
[NotNull]
public static IList<TItem> Filter<TItem, TState>([NotNull] this IList<TItem> items, TState state, Func<TItem, TState, bool> filter)
{
var hasIgnoredLogEvents = false;
IList<TItem> filterLogEvents = null;
for (var i = 0; i < items.Count; ++i)
{
var item = items[i];
if (filter(item, state))
{
if (hasIgnoredLogEvents && filterLogEvents == null)
{
filterLogEvents = new List<TItem>();
}

filterLogEvents?.Add(item);
}
else
{
if (!hasIgnoredLogEvents && i > 0)
{
filterLogEvents = new List<TItem>();
for (var j = 0; j < i; ++j)
filterLogEvents.Add(items[j]);
}
hasIgnoredLogEvents = true;
}
}
return filterLogEvents ?? (hasIgnoredLogEvents ? ArrayHelper.Empty<TItem>() : items);
}
}
}
65 changes: 55 additions & 10 deletions src/NLog/Targets/Wrappers/FilteringTargetWrapper.cs
Expand Up @@ -34,10 +34,12 @@
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;
using NLog.Internal;

/// <summary>
/// Filters log entries based on a condition.
Expand All @@ -59,8 +61,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 +90,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 = CreateFilter(value); }

/// <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 +125,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 (ShouldLogEvent(logEvent, Filter))
{
WrappedTarget.WriteAsyncLogEvent(logEvent);
}
Expand All @@ -119,5 +134,35 @@ protected override void Write(AsyncLogEventInfo logEvent)
logEvent.Continuation(null);
}
}

/// <inheritdoc/>
protected override void Write(IList<AsyncLogEventInfo> logEvents)
{
var filterLogEvents = logEvents.Filter(Filter, ShouldLogEvent);
WrappedTarget.WriteAsyncLogEvents(filterLogEvents);
}

private static bool ShouldLogEvent(AsyncLogEventInfo logEvent, Filter filter)
{
var filterResult = filter.GetFilterResult(logEvent.LogEvent);
if (filterResult != FilterResult.Ignore && filterResult != FilterResult.IgnoreFinal)
{
return true;
}
else
{
logEvent.Continuation(null);
return false;
}
}

private static ConditionBasedFilter CreateFilter(ConditionExpression value)
{
if (value == null)
{
return null;
}
return new ConditionBasedFilter { Condition = value, DefaultFilterResult = FilterResult.Ignore };
}
}
}

0 comments on commit dfd3845

Please sign in to comment.