commit 94b7d9de1f6795a8f6cd36544191e7558a1071e4 Author: upstream Date: Fri Aug 28 13:24:33 2026 +0200 Upstream NLog/NLog state of the touched files before 229644059f diff --git a/src/NLog/Filters/ConditionBasedFilter.cs b/src/NLog/Filters/ConditionBasedFilter.cs new file mode 100644 index 0000000..1a04215 --- /dev/null +++ b/src/NLog/Filters/ConditionBasedFilter.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the specified condition is met. + /// + /// + /// Conditions are expressed using a simple language. + /// + /// Documentation on NLog Wiki + [Filter("when")] + public class ConditionBasedFilter : Filter + { + internal static readonly ConditionBasedFilter Empty = new ConditionBasedFilter(); + + /// + /// Gets or sets the condition expression. + /// + /// + public ConditionExpression Condition { get; set; } = ConditionLiteralExpression.Null; + + internal FilterResult FilterDefaultAction { get; set; } = FilterResult.Neutral; + + /// + protected override FilterResult Check(LogEventInfo logEvent) + { + var val = Condition?.Evaluate(logEvent); + return ConditionExpression.BoxedTrue.Equals(val) ? Action : FilterDefaultAction; + } + } +} diff --git a/src/NLog/Filters/Filter.cs b/src/NLog/Filters/Filter.cs new file mode 100644 index 0000000..779f4b7 --- /dev/null +++ b/src/NLog/Filters/Filter.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// An abstract filter class. Provides a way to eliminate log messages + /// based on properties other than logger name and log level. + /// + [NLogConfigurationItem] + public abstract class Filter + { + /// + /// Initializes a new instance of the class. + /// + protected Filter() + { + Action = FilterResult.Neutral; + } + + /// + /// Gets or sets the action to be taken when filter matches. + /// + /// + public FilterResult Action { get; set; } + + /// + /// Gets the result of evaluating filter against given log event. + /// + /// The log event. + /// Filter result. + internal FilterResult GetFilterResult(LogEventInfo logEvent) + { + return Check(logEvent); + } + + /// + /// Checks whether log event should be logged or not. + /// + /// Log event. + /// + /// - if the log event should be ignored
+ /// - if the filter doesn't want to decide
+ /// - if the log event should be logged
+ /// .
+ protected abstract FilterResult Check(LogEventInfo logEvent); + } +} diff --git a/src/NLog/Filters/LayoutBasedFilter.cs b/src/NLog/Filters/LayoutBasedFilter.cs new file mode 100644 index 0000000..bc047f5 --- /dev/null +++ b/src/NLog/Filters/LayoutBasedFilter.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// A base class for filters that are based on comparing a value to a layout. + /// + public abstract class LayoutBasedFilter : Filter + { + /// + /// Initializes a new instance of the class. + /// + protected LayoutBasedFilter() + { + } + + /// + /// Gets or sets the layout to be used to filter log messages. + /// + /// The layout. + /// + public Layout Layout { get; set; } = Layout.Empty; + } +} diff --git a/src/NLog/Filters/WhenContainsFilter.cs b/src/NLog/Filters/WhenContainsFilter.cs new file mode 100644 index 0000000..e5135c6 --- /dev/null +++ b/src/NLog/Filters/WhenContainsFilter.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the calculated layout contains the specified substring. + /// This filter is deprecated in favor of <when /> which is based on conditions. + /// + [Filter("whenContains")] + public class WhenContainsFilter : LayoutBasedFilter + { + /// + /// Gets or sets a value indicating whether to ignore case when comparing strings. + /// + /// + public bool IgnoreCase { get; set; } + + /// + /// Gets or sets the substring to be matched. + /// + /// + public string Substring { get; set; } = string.Empty; + + /// + 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; + } + } +} diff --git a/src/NLog/Filters/WhenEqualFilter.cs b/src/NLog/Filters/WhenEqualFilter.cs new file mode 100644 index 0000000..e8a66cb --- /dev/null +++ b/src/NLog/Filters/WhenEqualFilter.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the calculated layout is equal to the specified substring. + /// This filter is deprecated in favor of <when /> which is based on conditions. + /// + [Filter("whenEqual")] + public class WhenEqualFilter : LayoutBasedFilter + { + /// + /// Gets or sets a value indicating whether to ignore case when comparing strings. + /// + /// + public bool IgnoreCase { get; set; } + + /// + /// Gets or sets a string to compare the layout to. + /// + /// + public string CompareTo { get; set; } = string.Empty; + + /// + 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; + } + } +} diff --git a/src/NLog/Filters/WhenNotContainsFilter.cs b/src/NLog/Filters/WhenNotContainsFilter.cs new file mode 100644 index 0000000..53a4d54 --- /dev/null +++ b/src/NLog/Filters/WhenNotContainsFilter.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the calculated layout does NOT contain the specified substring. + /// This filter is deprecated in favor of <when /> which is based on conditions. + /// + [Filter("whenNotContains")] + public class WhenNotContainsFilter : LayoutBasedFilter + { + /// + /// Gets or sets the substring to be matched. + /// + /// + public string Substring { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to ignore case when comparing strings. + /// + /// + public bool IgnoreCase { get; set; } + + /// + 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; + } + } +} diff --git a/src/NLog/Filters/WhenNotEqualFilter.cs b/src/NLog/Filters/WhenNotEqualFilter.cs new file mode 100644 index 0000000..3abd781 --- /dev/null +++ b/src/NLog/Filters/WhenNotEqualFilter.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the calculated layout is NOT equal to the specified substring. + /// This filter is deprecated in favor of <when /> which is based on conditions. + /// + [Filter("whenNotEqual")] + public class WhenNotEqualFilter : LayoutBasedFilter + { + /// + /// Initializes a new instance of the class. + /// + public WhenNotEqualFilter() + { + } + + /// + /// Gets or sets a string to compare the layout to. + /// + /// + public string CompareTo { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to ignore case when comparing strings. + /// + /// + public bool IgnoreCase { get; set; } + + /// + 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; + } + } +} diff --git a/src/NLog/Filters/WhenRepeatedFilter.cs b/src/NLog/Filters/WhenRepeatedFilter.cs new file mode 100644 index 0000000..3914c23 --- /dev/null +++ b/src/NLog/Filters/WhenRepeatedFilter.cs @@ -0,0 +1,414 @@ +// +// Copyright (c) 2004-2024 Jaroslaw Kowalski , 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; + + /// + /// Matches when the result of the calculated layout has been repeated a moment ago + /// + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [Filter("whenRepeated")] + public class WhenRepeatedFilter : LayoutBasedFilter + { + private const int MaxInitialRenderBufferLength = 16384; + + /// + /// How long before a filter expires, and logging is accepted again + /// + /// + public int TimeoutSeconds { get; set; } = 10; + + /// + /// Max length of filter values, will truncate if above limit + /// + /// + public int MaxLength { get; set; } = 1000; + + /// + /// Applies the configured action to the initial logevent that starts the timeout period. + /// Used to configure that it should ignore all events until timeout. + /// + /// + public bool IncludeFirst { get; set; } + + /// + /// Max number of unique filter values to expect simultaneously + /// + /// + public int MaxFilterCacheSize { get; set; } = 50000; + + /// + /// Default number of unique filter values to expect, will automatically increase if needed + /// + /// + public int DefaultFilterCacheSize { get; set; } = 1000; + + /// + /// Insert FilterCount value into when an event is no longer filtered + /// + /// + public string FilterCountPropertyName { get; set; } = string.Empty; + + /// + /// Append FilterCount to the when an event is no longer filtered + /// + /// + public string FilterCountMessageAppendFormat { get; set; } = string.Empty; + + /// + /// Reuse internal buffers, and doesn't have to constantly allocate new buffers + /// + /// + [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; + + /// + /// Default buffer size for the internal buffers + /// + /// + public int OptimizeBufferDefaultLength { get; set; } = 1000; + + private readonly ReusableBuilderCreator ReusableLayoutBuilder = new ReusableBuilderCreator(); + + private readonly Dictionary _repeatFilter = new Dictionary(1000); + private readonly Stack> _objectPool = new Stack>(1000); + + /// + /// Checks whether log event should be logged or not. In case the LogEvent has just been repeated. + /// + /// Log event. + /// + /// - if the log event should be ignored
+ /// - if the filter doesn't want to decide
+ /// - if the log event should be logged
+ /// .
+ 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; + } + + /// + /// Uses object pooling, and prunes stale filter items when the pool runs dry + /// + 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; + } + + /// + /// Remove stale filter-value from the cache, and fill them into the pool for reuse + /// + 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(); + } + } + + /// + /// Renders the Log Event into a filter value, that is used for checking if just repeated + /// + 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); + } + + /// + /// Repeated LogEvent detected. Checks if it should activate filter-action + /// + 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; + } + + /// + /// Filter Value State (mutable) + /// + 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; } + } + + /// + /// Filter Lookup Key (immutable) + /// + private +#if !NETFRAMEWORK + readonly +#endif + struct FilterInfoKey : IEquatable + { + 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); + } + } + } +}