Upstream NLog/NLog state of the touched files before 229644059f

This commit is contained in:
upstream committed 2026-08-28 13:24:33 +02:00
commit 94b7d9de1f
8 files changed
+911

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using NLog.Conditions;
/// <summary>
/// Matches when the specified condition is met.
/// </summary>
/// <remarks>
/// Conditions are expressed using a simple language.
/// </remarks>
/// <seealso href="https://github.com/NLog/NLog/wiki/Conditions">Documentation on NLog Wiki</seealso>
[Filter("when")]
public class ConditionBasedFilter : Filter
{
internal static readonly ConditionBasedFilter Empty = new ConditionBasedFilter();
/// <summary>
/// Gets or sets the condition expression.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public ConditionExpression Condition { get; set; } = ConditionLiteralExpression.Null;
internal FilterResult FilterDefaultAction { get; set; } = FilterResult.Neutral;
/// <inheritdoc/>
protected override FilterResult Check(LogEventInfo logEvent)
{
var val = Condition?.Evaluate(logEvent);
return ConditionExpression.BoxedTrue.Equals(val) ? Action : FilterDefaultAction;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using NLog.Config;
/// <summary>
/// An abstract filter class. Provides a way to eliminate log messages
/// based on properties other than logger name and log level.
/// </summary>
[NLogConfigurationItem]
public abstract class Filter
{
/// <summary>
/// Initializes a new instance of the <see cref="Filter" /> class.
/// </summary>
protected Filter()
{
Action = FilterResult.Neutral;
}
/// <summary>
/// Gets or sets the action to be taken when filter matches.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public FilterResult Action { get; set; }
/// <summary>
/// Gets the result of evaluating filter against given log event.
/// </summary>
/// <param name="logEvent">The log event.</param>
/// <returns>Filter result.</returns>
internal FilterResult GetFilterResult(LogEventInfo logEvent)
{
return Check(logEvent);
}
/// <summary>
/// Checks whether log event should be logged or not.
/// </summary>
/// <param name="logEvent">Log event.</param>
/// <returns>
/// <see cref="FilterResult.Ignore"/> - if the log event should be ignored<br/>
/// <see cref="FilterResult.Neutral"/> - if the filter doesn't want to decide<br/>
/// <see cref="FilterResult.Log"/> - if the log event should be logged<br/>
/// .</returns>
protected abstract FilterResult Check(LogEventInfo logEvent);
}
}
+57
View File
@@ -0,0 +1,57 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using NLog.Layouts;
/// <summary>
/// A base class for filters that are based on comparing a value to a layout.
/// </summary>
public abstract class LayoutBasedFilter : Filter
{
/// <summary>
/// Initializes a new instance of the <see cref="LayoutBasedFilter" /> class.
/// </summary>
protected LayoutBasedFilter()
{
}
/// <summary>
/// Gets or sets the layout to be used to filter log messages.
/// </summary>
/// <value>The layout.</value>
/// <docgen category='Filtering Options' order='10' />
public Layout Layout { get; set; } = Layout.Empty;
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using System;
/// <summary>
/// Matches when the calculated layout contains the specified substring.
/// This filter is deprecated in favor of <c>&lt;when /&gt;</c> which is based on <a href="https://github.com/NLog/NLog/wiki/Conditions">conditions</a>.
/// </summary>
[Filter("whenContains")]
public class WhenContainsFilter : LayoutBasedFilter
{
/// <summary>
/// Gets or sets a value indicating whether to ignore case when comparing strings.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public bool IgnoreCase { get; set; }
/// <summary>
/// Gets or sets the substring to be matched.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string Substring { get; set; } = string.Empty;
/// <inheritdoc/>
protected override FilterResult Check(LogEventInfo logEvent)
{
StringComparison comparisonType = IgnoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
string result = Layout.Render(logEvent);
if (!string.IsNullOrEmpty(Substring) && result.IndexOf(Substring, comparisonType) >= 0)
{
return Action;
}
return FilterResult.Neutral;
}
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using System;
/// <summary>
/// Matches when the calculated layout is equal to the specified substring.
/// This filter is deprecated in favor of <c>&lt;when /&gt;</c> which is based on <a href="https://github.com/NLog/NLog/wiki/Conditions">conditions</a>.
/// </summary>
[Filter("whenEqual")]
public class WhenEqualFilter : LayoutBasedFilter
{
/// <summary>
/// Gets or sets a value indicating whether to ignore case when comparing strings.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public bool IgnoreCase { get; set; }
/// <summary>
/// Gets or sets a string to compare the layout to.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string CompareTo { get; set; } = string.Empty;
/// <inheritdoc/>
protected override FilterResult Check(LogEventInfo logEvent)
{
StringComparison comparisonType = IgnoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
string result = Layout.Render(logEvent);
if (result.Equals(CompareTo, comparisonType))
{
return Action;
}
return FilterResult.Neutral;
}
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using System;
/// <summary>
/// Matches when the calculated layout does NOT contain the specified substring.
/// This filter is deprecated in favor of <c>&lt;when /&gt;</c> which is based on <a href="https://github.com/NLog/NLog/wiki/Conditions">conditions</a>.
/// </summary>
[Filter("whenNotContains")]
public class WhenNotContainsFilter : LayoutBasedFilter
{
/// <summary>
/// Gets or sets the substring to be matched.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string Substring { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to ignore case when comparing strings.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public bool IgnoreCase { get; set; }
/// <inheritdoc/>
protected override FilterResult Check(LogEventInfo logEvent)
{
StringComparison comparison = IgnoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
string result = Layout.Render(logEvent);
if (!string.IsNullOrEmpty(Substring) && result.IndexOf(Substring, comparison) < 0)
{
return Action;
}
return FilterResult.Neutral;
}
}
}
+79
View File
@@ -0,0 +1,79 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using System;
/// <summary>
/// Matches when the calculated layout is NOT equal to the specified substring.
/// This filter is deprecated in favor of <c>&lt;when /&gt;</c> which is based on <a href="https://github.com/NLog/NLog/wiki/Conditions">conditions</a>.
/// </summary>
[Filter("whenNotEqual")]
public class WhenNotEqualFilter : LayoutBasedFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="WhenNotEqualFilter" /> class.
/// </summary>
public WhenNotEqualFilter()
{
}
/// <summary>
/// Gets or sets a string to compare the layout to.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string CompareTo { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to ignore case when comparing strings.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public bool IgnoreCase { get; set; }
/// <inheritdoc/>
protected override FilterResult Check(LogEventInfo logEvent)
{
StringComparison comparisonType = IgnoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
string result = Layout.Render(logEvent);
if (!result.Equals(CompareTo, comparisonType))
{
return Action;
}
return FilterResult.Neutral;
}
}
}
+414
View File
@@ -0,0 +1,414 @@
//
// Copyright (c) 2004-2024 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.Filters
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using NLog.Internal;
/// <summary>
/// Matches when the result of the calculated layout has been repeated a moment ago
/// </summary>
/// <remarks>
/// <a href="https://github.com/NLog/NLog/wiki/WhenRepeated-Filter">See NLog Wiki</a>
/// </remarks>
/// <seealso href="https://github.com/NLog/NLog/wiki/WhenRepeated-Filter">Documentation on NLog Wiki</seealso>
[Filter("whenRepeated")]
public class WhenRepeatedFilter : LayoutBasedFilter
{
private const int MaxInitialRenderBufferLength = 16384;
/// <summary>
/// How long before a filter expires, and logging is accepted again
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public int TimeoutSeconds { get; set; } = 10;
/// <summary>
/// Max length of filter values, will truncate if above limit
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public int MaxLength { get; set; } = 1000;
/// <summary>
/// Applies the configured action to the initial logevent that starts the timeout period.
/// Used to configure that it should ignore all events until timeout.
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public bool IncludeFirst { get; set; }
/// <summary>
/// Max number of unique filter values to expect simultaneously
/// </summary>
/// <docgen category='Filtering Options' order='100' />
public int MaxFilterCacheSize { get; set; } = 50000;
/// <summary>
/// Default number of unique filter values to expect, will automatically increase if needed
/// </summary>
/// <docgen category='Filtering Options' order='100' />
public int DefaultFilterCacheSize { get; set; } = 1000;
/// <summary>
/// Insert FilterCount value into <see cref="LogEventInfo.Properties"/> when an event is no longer filtered
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string FilterCountPropertyName { get; set; } = string.Empty;
/// <summary>
/// Append FilterCount to the <see cref="LogEventInfo.Message"/> when an event is no longer filtered
/// </summary>
/// <docgen category='Filtering Options' order='10' />
public string FilterCountMessageAppendFormat { get; set; } = string.Empty;
/// <summary>
/// Reuse internal buffers, and doesn't have to constantly allocate new buffers
/// </summary>
/// <docgen category='Filtering Options' order='100' />
[Obsolete("No longer used, and always returns true. Marked obsolete on NLog 5.0")]
[EditorBrowsable(EditorBrowsableState.Never)]
public bool OptimizeBufferReuse { get => _optimizeBufferReuse ?? true; set => _optimizeBufferReuse = value ? true : (bool?)null; }
private bool? _optimizeBufferReuse;
/// <summary>
/// Default buffer size for the internal buffers
/// </summary>
/// <docgen category='Filtering Options' order='100' />
public int OptimizeBufferDefaultLength { get; set; } = 1000;
private readonly ReusableBuilderCreator ReusableLayoutBuilder = new ReusableBuilderCreator();
private readonly Dictionary<FilterInfoKey, FilterInfo> _repeatFilter = new Dictionary<FilterInfoKey, FilterInfo>(1000);
private readonly Stack<KeyValuePair<FilterInfoKey, FilterInfo>> _objectPool = new Stack<KeyValuePair<FilterInfoKey, FilterInfo>>(1000);
/// <summary>
/// Checks whether log event should be logged or not. In case the LogEvent has just been repeated.
/// </summary>
/// <param name="logEvent">Log event.</param>
/// <returns>
/// <see cref="FilterResult.Ignore"/> - if the log event should be ignored<br/>
/// <see cref="FilterResult.Neutral"/> - if the filter doesn't want to decide<br/>
/// <see cref="FilterResult.Log"/> - if the log event should be logged<br/>
/// .</returns>
protected override FilterResult Check(LogEventInfo logEvent)
{
FilterResult filterResult = FilterResult.Neutral;
bool obsoleteFilter = false;
lock (_repeatFilter)
{
using (var targetBuilder = ReusableLayoutBuilder.Allocate())
{
if (targetBuilder.Result.Capacity != OptimizeBufferDefaultLength)
{
// StringBuilder.Equals only works when StringBuilder.Capacity is the same
if (OptimizeBufferDefaultLength < MaxInitialRenderBufferLength)
{
OptimizeBufferDefaultLength = MaxLength;
while (OptimizeBufferDefaultLength < targetBuilder.Result.Capacity && OptimizeBufferDefaultLength < MaxInitialRenderBufferLength)
{
OptimizeBufferDefaultLength *= 2;
}
}
targetBuilder.Result.Capacity = OptimizeBufferDefaultLength;
}
FilterInfoKey filterInfoKey = RenderFilterInfoKey(logEvent, targetBuilder.Result);
if (!_repeatFilter.TryGetValue(filterInfoKey, out var filterInfo))
{
filterInfo = CreateFilterInfo(logEvent);
if (filterInfo.StringBuffer != null)
{
filterInfo.StringBuffer.ClearBuilder();
int length = Math.Min(targetBuilder.Result.Length, MaxLength);
for (int i = 0; i < length; ++i)
filterInfo.StringBuffer.Append(targetBuilder.Result[i]);
}
filterInfo.Refresh(logEvent.Level, logEvent.TimeStamp, 0);
_repeatFilter.Add(new FilterInfoKey(filterInfo.StringBuffer, filterInfoKey.StringValue, filterInfoKey.StringHashCode), filterInfo);
obsoleteFilter = true;
}
else
{
if (IncludeFirst)
{
obsoleteFilter = filterInfo.IsObsolete(logEvent.TimeStamp, TimeoutSeconds);
}
filterResult = RefreshFilterInfo(logEvent, filterInfo);
}
}
}
// Ignore the first log-event, and wait until next timeout expiry
if (IncludeFirst && obsoleteFilter)
{
filterResult = Action;
}
return filterResult;
}
/// <summary>
/// Uses object pooling, and prunes stale filter items when the pool runs dry
/// </summary>
private FilterInfo CreateFilterInfo(LogEventInfo logEvent)
{
FilterInfo reusableObject;
if (_objectPool.Count == 0 && _repeatFilter.Count > DefaultFilterCacheSize)
{
int aggressiveTimeoutSeconds = _repeatFilter.Count > MaxFilterCacheSize ? TimeoutSeconds * 2 / 3 : TimeoutSeconds;
PruneFilterCache(logEvent, Math.Max(1, aggressiveTimeoutSeconds));
if (_repeatFilter.Count > MaxFilterCacheSize)
{
PruneFilterCache(logEvent, Math.Max(1, TimeoutSeconds / 2));
}
}
if (_objectPool.Count == 0)
{
reusableObject = new FilterInfo(new StringBuilder(OptimizeBufferDefaultLength));
}
else
{
reusableObject = _objectPool.Pop().Value;
// StringBuilder.Equals only works when StringBuilder.Capacity is the same
if (reusableObject.StringBuffer != null && reusableObject.StringBuffer.Capacity != OptimizeBufferDefaultLength)
{
reusableObject.StringBuffer.Capacity = OptimizeBufferDefaultLength;
}
}
return reusableObject;
}
/// <summary>
/// Remove stale filter-value from the cache, and fill them into the pool for reuse
/// </summary>
private void PruneFilterCache(LogEventInfo logEvent, int aggressiveTimeoutSeconds)
{
foreach (var filterPair in _repeatFilter)
{
if (filterPair.Value.IsObsolete(logEvent.TimeStamp, aggressiveTimeoutSeconds))
{
_objectPool.Push(filterPair);
}
}
foreach (var filterPair in _objectPool)
{
_repeatFilter.Remove(filterPair.Key);
}
if (_repeatFilter.Count * 2 > DefaultFilterCacheSize && DefaultFilterCacheSize < MaxFilterCacheSize)
{
DefaultFilterCacheSize *= 2;
}
while (_objectPool.Count != 0 && _objectPool.Count > DefaultFilterCacheSize)
{
_objectPool.Pop();
}
}
/// <summary>
/// Renders the Log Event into a filter value, that is used for checking if just repeated
/// </summary>
private FilterInfoKey RenderFilterInfoKey(LogEventInfo logEvent, StringBuilder targetBuilder)
{
if (targetBuilder != null)
{
Layout.Render(logEvent, targetBuilder);
if (targetBuilder.Length > MaxLength)
targetBuilder.Length = MaxLength;
return new FilterInfoKey(targetBuilder, null);
}
string value = Layout.Render(logEvent);
if (value.Length > MaxLength)
value = value.Substring(0, MaxLength);
return new FilterInfoKey(null, value);
}
/// <summary>
/// Repeated LogEvent detected. Checks if it should activate filter-action
/// </summary>
private FilterResult RefreshFilterInfo(LogEventInfo logEvent, FilterInfo filterInfo)
{
if (filterInfo.HasExpired(logEvent.TimeStamp, TimeoutSeconds) || logEvent.Level > filterInfo.LogLevel)
{
int filterCount = filterInfo.FilterCount;
if (filterCount > 0 && filterInfo.IsObsolete(logEvent.TimeStamp, TimeoutSeconds))
{
filterCount = 0;
}
filterInfo.Refresh(logEvent.Level, logEvent.TimeStamp, 0);
if (filterCount > 0)
{
if (!string.IsNullOrEmpty(FilterCountPropertyName))
{
if (!logEvent.Properties.TryGetValue(FilterCountPropertyName, out var otherFilterCount))
{
logEvent.Properties[FilterCountPropertyName] = filterCount;
}
else if (otherFilterCount is int i)
{
filterCount = Math.Max(i, filterCount);
logEvent.Properties[FilterCountPropertyName] = filterCount;
}
}
if (!string.IsNullOrEmpty(FilterCountMessageAppendFormat) && logEvent.Message != null)
{
logEvent.Message += string.Format(FilterCountMessageAppendFormat, filterCount.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
}
return FilterResult.Neutral;
}
filterInfo.Refresh(logEvent.Level, logEvent.TimeStamp, filterInfo.FilterCount + 1);
return Action;
}
/// <summary>
/// Filter Value State (mutable)
/// </summary>
private sealed class FilterInfo
{
public FilterInfo(StringBuilder stringBuilder)
{
StringBuffer = stringBuilder;
}
public void Refresh(LogLevel logLevel, DateTime logTimeStamp, int filterCount)
{
if (filterCount == 0)
{
LastLogTime = logTimeStamp;
LogLevel = logLevel;
}
else if (LogLevel is null || logLevel.Ordinal > LogLevel.Ordinal)
{
LogLevel = logLevel;
}
LastFilterTime = logTimeStamp;
FilterCount = filterCount;
}
public bool IsObsolete(DateTime logEventTime, int timeoutSeconds)
{
if (FilterCount == 0)
{
return HasExpired(logEventTime, timeoutSeconds);
}
return (logEventTime - LastFilterTime).TotalSeconds > timeoutSeconds && HasExpired(logEventTime, timeoutSeconds * 2);
}
public bool HasExpired(DateTime logEventTime, int timeoutSeconds)
{
return (logEventTime - LastLogTime).TotalSeconds > timeoutSeconds;
}
public StringBuilder StringBuffer { get; }
public LogLevel? LogLevel { get; private set; }
private DateTime LastLogTime { get; set; }
private DateTime LastFilterTime { get; set; }
public int FilterCount { get; private set; }
}
/// <summary>
/// Filter Lookup Key (immutable)
/// </summary>
private
#if !NETFRAMEWORK
readonly
#endif
struct FilterInfoKey : IEquatable<FilterInfoKey>
{
private readonly StringBuilder? _stringBuffer;
public readonly string? StringValue;
public readonly int StringHashCode;
public FilterInfoKey(StringBuilder? stringBuffer, string? stringValue, int? stringHashCode = null)
{
_stringBuffer = stringBuffer;
StringValue = stringValue;
if (stringHashCode.HasValue)
{
StringHashCode = stringHashCode.Value;
}
else if (stringBuffer != null)
{
int hashCode = stringBuffer.Length.GetHashCode();
int hashCodeCount = Math.Min(stringBuffer.Length, 100);
for (int i = 0; i < hashCodeCount; ++i)
hashCode = hashCode ^ stringBuffer[i].GetHashCode();
StringHashCode = hashCode;
}
else
{
StringHashCode = stringValue is null ? 0 : StringComparer.Ordinal.GetHashCode(stringValue);
}
}
public override int GetHashCode()
{
return StringHashCode;
}
public bool Equals(FilterInfoKey other)
{
if (StringValue != null)
{
return string.Equals(StringValue, other.StringValue, StringComparison.Ordinal);
}
if (_stringBuffer != null && other._stringBuffer != null)
{
// StringBuilder.Equals only works when StringBuilder.Capacity is the same
if (_stringBuffer.Capacity != other._stringBuffer.Capacity)
{
return _stringBuffer.EqualTo(other._stringBuffer);
}
return _stringBuffer.Equals(other._stringBuffer);
}
return ReferenceEquals(_stringBuffer, other._stringBuffer) && ReferenceEquals(StringValue, other.StringValue);
}
public override bool Equals(object? obj)
{
return obj is FilterInfoKey key && Equals(key);
}
}
}
}