diff --git a/src/NLog/Config/AssemblyExtensionTypes.cs b/src/NLog/Config/AssemblyExtensionTypes.cs new file mode 100644 index 0000000..b69bce2 --- /dev/null +++ b/src/NLog/Config/AssemblyExtensionTypes.cs @@ -0,0 +1,278 @@ +// +// 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.Config +{ + /// + /// Provides logging interface and utility functions. + /// + internal static class AssemblyExtensionTypes + { +#pragma warning disable CS0618 // Type or member is obsolete + public static void RegisterTargetTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var targetFactory = factory.GetTargetFactory(); + + factory.RegisterTypeProperties(() => null); +#if NETFRAMEWORK + targetFactory.RegisterType("EventLog", checkTypeExists); +#endif + targetFactory.RegisterType("ColoredConsole", checkTypeExists); + factory.RegisterType(); + targetFactory.RegisterType("Console", checkTypeExists); + factory.RegisterType(); + targetFactory.RegisterType("Debugger", checkTypeExists); + targetFactory.RegisterType("DebugSystem", checkTypeExists); + targetFactory.RegisterType("Debug", checkTypeExists); + targetFactory.RegisterType("File", checkTypeExists); + targetFactory.RegisterType("Memory", checkTypeExists); + factory.RegisterType(); + targetFactory.RegisterType("MethodCall", checkTypeExists); + targetFactory.RegisterType("Null", checkTypeExists); + factory.RegisterType(); + targetFactory.RegisterType("AsyncWrapper", checkTypeExists); + targetFactory.RegisterType("AutoFlushWrapper", checkTypeExists); + targetFactory.RegisterType("BufferingWrapper", checkTypeExists); + targetFactory.RegisterType("FallbackGroup", checkTypeExists); + factory.RegisterType(); + targetFactory.RegisterType("FilteringWrapper", checkTypeExists); + targetFactory.RegisterType("GroupByWrapper", checkTypeExists); + targetFactory.RegisterType("LimitingWrapper", checkTypeExists); + targetFactory.RegisterType("PostFilteringWrapper", checkTypeExists); + targetFactory.RegisterType("RandomizeGroup", checkTypeExists); + targetFactory.RegisterType("RepeatingWrapper", checkTypeExists); + targetFactory.RegisterType("RetryingWrapper", checkTypeExists); + targetFactory.RegisterType("RoundRobinGroup", checkTypeExists); + targetFactory.RegisterType("SplitGroup", checkTypeExists); + } + + public static void RegisterLayoutTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var layoutFactory = factory.GetLayoutFactory(); + + factory.RegisterTypeProperties(() => null); + layoutFactory.RegisterType("CompoundLayout", checkTypeExists); + factory.RegisterType(); + layoutFactory.RegisterType("CsvLayout", checkTypeExists); + layoutFactory.RegisterType("JsonArrayLayout", checkTypeExists); + factory.RegisterType(); + layoutFactory.RegisterType("JsonLayout", checkTypeExists); + layoutFactory.RegisterType("LayoutWithHeaderAndFooter", checkTypeExists); + layoutFactory.RegisterType("SimpleLayout", checkTypeExists); + factory.RegisterType(); + factory.RegisterType(); + layoutFactory.RegisterType("XmlLayout", checkTypeExists); + } + + public static void RegisterLayoutRendererTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var layoutRendererFactory = factory.GetLayoutRendererFactory(); + var ambientPropertyFactory = factory.GetAmbientPropertyFactory(); + +#if NETFRAMEWORK + layoutRendererFactory.RegisterType("appsetting", checkTypeExists); +#endif + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + layoutRendererFactory.RegisterType("alleventproperties", checkTypeExists); + layoutRendererFactory.RegisterType("appdomain", checkTypeExists); + layoutRendererFactory.RegisterType("assemblyversion", checkTypeExists); + layoutRendererFactory.RegisterType("basedir", checkTypeExists); + layoutRendererFactory.RegisterType("callsitefilename", checkTypeExists); + layoutRendererFactory.RegisterType("callsite", checkTypeExists); + layoutRendererFactory.RegisterType("callsitelinenumber", checkTypeExists); + layoutRendererFactory.RegisterType("counter", checkTypeExists); + layoutRendererFactory.RegisterType("currentdir", checkTypeExists); + layoutRendererFactory.RegisterType("date", checkTypeExists); + layoutRendererFactory.RegisterType("dbnull", checkTypeExists); + layoutRendererFactory.RegisterType("dirseparator", checkTypeExists); + layoutRendererFactory.RegisterType("environment", checkTypeExists); + layoutRendererFactory.RegisterType("environmentuser", checkTypeExists); + layoutRendererFactory.RegisterType("eventproperties", checkTypeExists); + layoutRendererFactory.RegisterType("eventproperty", checkTypeExists); + layoutRendererFactory.RegisterType("eventcontext", checkTypeExists); + layoutRendererFactory.RegisterType("exceptiondata", checkTypeExists); + layoutRendererFactory.RegisterType("exception", checkTypeExists); + layoutRendererFactory.RegisterType("gc", checkTypeExists); + layoutRendererFactory.RegisterType("gdc", checkTypeExists); + layoutRendererFactory.RegisterType("guid", checkTypeExists); + layoutRendererFactory.RegisterType("hostname", checkTypeExists); + layoutRendererFactory.RegisterType("identity", checkTypeExists); + layoutRendererFactory.RegisterType("installcontext", checkTypeExists); + layoutRendererFactory.RegisterType("level", checkTypeExists); + layoutRendererFactory.RegisterType("loglevel", checkTypeExists); + layoutRendererFactory.RegisterType("literal", checkTypeExists); + layoutRendererFactory.RegisterType("loggername", checkTypeExists); + layoutRendererFactory.RegisterType("logger", checkTypeExists); + layoutRendererFactory.RegisterType("longdate", checkTypeExists); + layoutRendererFactory.RegisterType("machinename", checkTypeExists); + layoutRendererFactory.RegisterType("message", checkTypeExists); + layoutRendererFactory.RegisterType("newline", checkTypeExists); + layoutRendererFactory.RegisterType("nlogdir", checkTypeExists); + layoutRendererFactory.RegisterType("processdir", checkTypeExists); + layoutRendererFactory.RegisterType("processid", checkTypeExists); + layoutRendererFactory.RegisterType("processinfo", checkTypeExists); + layoutRendererFactory.RegisterType("processname", checkTypeExists); + layoutRendererFactory.RegisterType("processstart", checkTypeExists); + layoutRendererFactory.RegisterType("processtime", checkTypeExists); + layoutRendererFactory.RegisterType("scopeindent", checkTypeExists); + layoutRendererFactory.RegisterType("scopenested", checkTypeExists); + layoutRendererFactory.RegisterType("ndc", checkTypeExists); + layoutRendererFactory.RegisterType("ndlc", checkTypeExists); + layoutRendererFactory.RegisterType("scopeproperty", checkTypeExists); + layoutRendererFactory.RegisterType("mdc", checkTypeExists); + layoutRendererFactory.RegisterType("mdlc", checkTypeExists); + layoutRendererFactory.RegisterType("scopetiming", checkTypeExists); + layoutRendererFactory.RegisterType("ndlctiming", checkTypeExists); + layoutRendererFactory.RegisterType("sequenceid", checkTypeExists); + layoutRendererFactory.RegisterType("shortdate", checkTypeExists); + layoutRendererFactory.RegisterType("userApplicationDataDir", checkTypeExists); + layoutRendererFactory.RegisterType("commonApplicationDataDir", checkTypeExists); + layoutRendererFactory.RegisterType("specialfolder", checkTypeExists); + layoutRendererFactory.RegisterType("userLocalApplicationDataDir", checkTypeExists); + layoutRendererFactory.RegisterType("stacktrace", checkTypeExists); + layoutRendererFactory.RegisterType("tempdir", checkTypeExists); + layoutRendererFactory.RegisterType("threadid", checkTypeExists); + layoutRendererFactory.RegisterType("threadname", checkTypeExists); + layoutRendererFactory.RegisterType("ticks", checkTypeExists); + layoutRendererFactory.RegisterType("time", checkTypeExists); + layoutRendererFactory.RegisterType("var", checkTypeExists); + layoutRendererFactory.RegisterType("cached", checkTypeExists); + ambientPropertyFactory.RegisterType("Cached", checkTypeExists); + ambientPropertyFactory.RegisterType("ClearCache", checkTypeExists); + ambientPropertyFactory.RegisterType("CachedSeconds", checkTypeExists); + layoutRendererFactory.RegisterType("filesystemnormalize", checkTypeExists); + ambientPropertyFactory.RegisterType("FSNormalize", checkTypeExists); + layoutRendererFactory.RegisterType("jsonencode", checkTypeExists); + ambientPropertyFactory.RegisterType("JsonEncode", checkTypeExists); + layoutRendererFactory.RegisterType("left", checkTypeExists); + ambientPropertyFactory.RegisterType("Truncate", checkTypeExists); + layoutRendererFactory.RegisterType("lowercase", checkTypeExists); + ambientPropertyFactory.RegisterType("Lowercase", checkTypeExists); + ambientPropertyFactory.RegisterType("ToLower", checkTypeExists); + layoutRendererFactory.RegisterType("norawvalue", checkTypeExists); + ambientPropertyFactory.RegisterType("NoRawValue", checkTypeExists); + layoutRendererFactory.RegisterType("ObjectPath", checkTypeExists); + ambientPropertyFactory.RegisterType("ObjectPath", checkTypeExists); + layoutRendererFactory.RegisterType("onexception", checkTypeExists); + layoutRendererFactory.RegisterType("onhasproperties", checkTypeExists); + layoutRendererFactory.RegisterType("pad", checkTypeExists); + ambientPropertyFactory.RegisterType("Padding", checkTypeExists); + ambientPropertyFactory.RegisterType("PadCharacter", checkTypeExists); + ambientPropertyFactory.RegisterType("FixedLength", checkTypeExists); + ambientPropertyFactory.RegisterType("AlignmentOnTruncation", checkTypeExists); + layoutRendererFactory.RegisterType("replace", checkTypeExists); + layoutRendererFactory.RegisterType("replacenewlines", checkTypeExists); + ambientPropertyFactory.RegisterType("ReplaceNewLines", checkTypeExists); + layoutRendererFactory.RegisterType("right", checkTypeExists); + layoutRendererFactory.RegisterType("rot13", checkTypeExists); + layoutRendererFactory.RegisterType("substring", checkTypeExists); + layoutRendererFactory.RegisterType("trimwhitespace", checkTypeExists); + ambientPropertyFactory.RegisterType("TrimWhiteSpace", checkTypeExists); + layoutRendererFactory.RegisterType("uppercase", checkTypeExists); + ambientPropertyFactory.RegisterType("Uppercase", checkTypeExists); + ambientPropertyFactory.RegisterType("ToUpper", checkTypeExists); + layoutRendererFactory.RegisterType("urlencode", checkTypeExists); + layoutRendererFactory.RegisterType("whenEmpty", checkTypeExists); + ambientPropertyFactory.RegisterType("WhenEmpty", checkTypeExists); + layoutRendererFactory.RegisterType("when", checkTypeExists); + ambientPropertyFactory.RegisterType("When", checkTypeExists); + layoutRendererFactory.RegisterType("wrapline", checkTypeExists); + ambientPropertyFactory.RegisterType("WrapLine", checkTypeExists); + layoutRendererFactory.RegisterType("xmlencode", checkTypeExists); + ambientPropertyFactory.RegisterType("XmlEncode", checkTypeExists); + } + + public static void RegisterFilterTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var filterFactory = factory.GetFilterFactory(); + filterFactory.RegisterType("when", checkTypeExists); + filterFactory.RegisterType("whenContains", checkTypeExists); + filterFactory.RegisterType("whenEqual", checkTypeExists); + filterFactory.RegisterType("whenNotContains", checkTypeExists); + filterFactory.RegisterType("whenNotEqual", checkTypeExists); + filterFactory.RegisterType("whenRepeated", checkTypeExists); + } + + public static void RegisterTimeSourceTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var timeSourceFactory = factory.GetTimeSourceFactory(); + timeSourceFactory.RegisterType("AccurateLocal", checkTypeExists); + timeSourceFactory.RegisterType("AccurateUTC", checkTypeExists); + timeSourceFactory.RegisterType("FastLocal", checkTypeExists); + timeSourceFactory.RegisterType("FastUTC", checkTypeExists); + } + + public static void RegisterConditionTypes(ConfigurationItemFactory factory, bool checkTypeExists) + { + var conditionMethodFactory = factory.GetConditionMethodFactory(); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + factory.RegisterTypeProperties(() => null); + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("length")) + conditionMethodFactory.RegisterOneParameter("length", (logEvent, arg1) => NLog.Conditions.ConditionMethods.Length(arg1?.ToString())); + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("equals")) + conditionMethodFactory.RegisterTwoParameters("equals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString()) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("strequals")) + { + conditionMethodFactory.RegisterTwoParameters("strequals", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString()) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + conditionMethodFactory.RegisterThreeParameters("strequals", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Equals2(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + } + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("contains")) + { + conditionMethodFactory.RegisterTwoParameters("contains", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString()) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + conditionMethodFactory.RegisterThreeParameters("contains", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.Contains(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + } + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("starts-with")) + { + conditionMethodFactory.RegisterTwoParameters("starts-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString()) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + conditionMethodFactory.RegisterThreeParameters("starts-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.StartsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + } + if (!checkTypeExists || !conditionMethodFactory.CheckTypeAliasExists("ends-with")) + { + conditionMethodFactory.RegisterTwoParameters("ends-with", (logEvent, arg1, arg2) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString()) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + conditionMethodFactory.RegisterThreeParameters("ends-with", (logEvent, arg1, arg2, arg3) => NLog.Conditions.ConditionMethods.EndsWith(arg1?.ToString(), arg2?.ToString(), arg3 is bool ignoreCase ? ignoreCase : true) ? Conditions.ConditionExpression.BoxedTrue : Conditions.ConditionExpression.BoxedFalse); + } + } +#pragma warning restore CS0618 // Type or member is obsolete + } +} diff --git a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs new file mode 100644 index 0000000..c1b9aa4 --- /dev/null +++ b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs @@ -0,0 +1,181 @@ +// +// 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.LayoutRenderers.Wrappers +{ + using System; + using NLog.Config; + using NLog.Internal; + using NLog.Layouts; + + /// + /// Applies caching to another layout output. + /// + /// + /// The value of the inner layout will be rendered only once and reused subsequently. + /// + /// See NLog Wiki + /// + /// Documentation on NLog Wiki + [LayoutRenderer("cached")] + [AmbientProperty(nameof(Cached))] + [AmbientProperty(nameof(ClearCache))] + [AmbientProperty(nameof(CachedSeconds))] + [AppDomainFixedOutput] + [ThreadAgnostic] + public sealed class CachedLayoutRendererWrapper : WrapperLayoutRendererBase, IStringValueRenderer + { + /// + /// A value indicating when the cache is cleared. + /// + [Flags] + public enum ClearCacheOption + { + /// Never clear the cache. + None = 0, + /// Clear the cache whenever the is initialized. + OnInit = 1, + /// Clear the cache whenever the is closed. + OnClose = 2 + } + + private readonly object _lockObject = new object(); + private string? _cachedValue; + private string? _renderedCacheKey; + private DateTime _cachedValueExpires; + private TimeSpan? _cachedValueTimeout; + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// Default: + /// + public bool Cached { get; set; } = true; + + /// + /// Gets or sets a value indicating when the cache is cleared. + /// + /// Default: | + /// + public ClearCacheOption ClearCache { get; set; } = ClearCacheOption.OnInit | ClearCacheOption.OnClose; + + /// + /// Gets or sets whether to reset cached value when CacheKey output changes. Example CacheKey could render current day, so the cached-value is reset on day roll. + /// + /// Default: + /// + public Layout? CacheKey { get; set; } + + /// + /// Gets or sets a value indicating how many seconds the value should stay cached until it expires + /// + /// Default: + /// + public int CachedSeconds + { + get => (int)(_cachedValueTimeout?.TotalSeconds ?? 0.0); + set + { + _cachedValueTimeout = TimeSpan.FromSeconds(value); + if (_cachedValueTimeout > TimeSpan.Zero) + Cached = true; + } + } + + /// + protected override void InitializeLayoutRenderer() + { + base.InitializeLayoutRenderer(); + if ((ClearCache & ClearCacheOption.OnInit) == ClearCacheOption.OnInit) + _cachedValue = null; + } + + /// + protected override void CloseLayoutRenderer() + { + base.CloseLayoutRenderer(); + if ((ClearCache & ClearCacheOption.OnClose) == ClearCacheOption.OnClose) + _cachedValue = null; + } + + /// + protected override string Transform(string text) + { + return text; + } + + /// + protected override string RenderInner(LogEventInfo logEvent) + { + if (Cached) + { + var newCacheKey = CacheKey?.Render(logEvent) ?? string.Empty; + var cachedValue = LookupValidCachedValue(logEvent, newCacheKey); + + if (cachedValue is null) + { + lock (_lockObject) + { + cachedValue = LookupValidCachedValue(logEvent, newCacheKey); + if (cachedValue is null) + { + _cachedValue = cachedValue = base.RenderInner(logEvent); + _renderedCacheKey = newCacheKey; + if (_cachedValueTimeout.HasValue) + _cachedValueExpires = logEvent.TimeStamp + _cachedValueTimeout.Value; + } + } + } + + return cachedValue; + } + else + { + return base.RenderInner(logEvent); + } + } + + string? LookupValidCachedValue(LogEventInfo logEvent, string newCacheKey) + { + if (_renderedCacheKey != newCacheKey) + return null; + + if (_cachedValueTimeout.HasValue && logEvent.TimeStamp > _cachedValueExpires) + return null; + + return _cachedValue; + } + + string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null; + } +} diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs new file mode 100644 index 0000000..ad401f7 --- /dev/null +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs @@ -0,0 +1,131 @@ +// +// 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. +// + +using NLog.Config; + +namespace NLog.UnitTests.LayoutRenderers.Wrappers +{ + using System.Linq; + using NLog; + using NLog.Layouts; + using Xunit; + + public class CachedTests : NLogTestBase + { + [Fact] + public void CachedLayoutRendererWrapper() + { + SimpleLayout l = "${guid}"; + + string s1 = l.Render(LogEventInfo.CreateNullEvent()); + string s2 = l.Render(LogEventInfo.CreateNullEvent()); + string s3; + + // normally GUIDs are never the same + Assert.NotEqual(s1, s2); + + // but when you apply ${cached}, the guid will only be generated once + l = "${cached:${guid}:cached=true}"; + s1 = l.Render(LogEventInfo.CreateNullEvent()); + s2 = l.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal(s1, s2); + + // calling Close() on Layout Renderer will reset the cached value + l.LayoutRenderers.First().Close(); + s3 = l.Render(LogEventInfo.CreateNullEvent()); + Assert.NotEqual(s2, s3); + + // unless we use clearcache=none + l = "${cached:${guid}:cached=true:clearcache=none}"; + s1 = l.Render(LogEventInfo.CreateNullEvent()); + l.LayoutRenderers.First().Close(); + s2 = l.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal(s1, s2); + + // another way to achieve the same thing is using cached=true + l = "${guid:cached=true}"; + s1 = l.Render(LogEventInfo.CreateNullEvent()); + s2 = l.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal(s1, s2); + + // another way to achieve the same thing is using cached=true + l = "${guid:cached=false}"; + s1 = l.Render(LogEventInfo.CreateNullEvent()); + s2 = l.Render(LogEventInfo.CreateNullEvent()); + Assert.NotEqual(s1, s2); + } + + /// + /// test the cachekey + /// + [Fact] + public void CacheKeyTest() + { + LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(@" + + + + + + + + "); + LogManager.Configuration.Variables["var1"] = "a"; + + var logger = LogManager.GetLogger("A"); + logger.Debug("msg"); + var s1 = GetDebugLastMessage("debug"); + logger.Debug("msg"); + var s2 = GetDebugLastMessage("debug"); + Assert.NotEmpty(s1); + Assert.Equal(s1, s2); + //change var will reset cache + LogManager.Configuration.Variables["var1"] = "b"; + logger.Debug("msg"); + var s3 = GetDebugLastMessage("debug"); + Assert.NotEmpty(s3); + Assert.NotEqual(s1, s3); + } + + [Fact] + public void CachedSecondsTimeoutTest() + { + SimpleLayout l = "${guid:cachedSeconds=60}"; + var s1 = l.Render(LogEventInfo.CreateNullEvent()); + var s2 = l.Render(new LogEventInfo()); + Assert.Equal(s1, s2); + var s3 = l.Render(new LogEventInfo() { TimeStamp = NLog.Time.TimeSource.Current.Time.AddMinutes(2) }); + Assert.NotEqual(s2, s3); + } + } +}