diff --git a/src/NLog/Config/AssemblyExtensionTypes.cs b/src/NLog/Config/AssemblyExtensionTypes.cs index b69bce2..512f62f 100644 --- a/src/NLog/Config/AssemblyExtensionTypes.cs +++ b/src/NLog/Config/AssemblyExtensionTypes.cs @@ -172,6 +172,7 @@ namespace NLog.Config ambientPropertyFactory.RegisterType("Cached", checkTypeExists); ambientPropertyFactory.RegisterType("ClearCache", checkTypeExists); ambientPropertyFactory.RegisterType("CachedSeconds", checkTypeExists); + ambientPropertyFactory.RegisterType("CachedUntil", checkTypeExists); layoutRendererFactory.RegisterType("filesystemnormalize", checkTypeExists); ambientPropertyFactory.RegisterType("FSNormalize", checkTypeExists); layoutRendererFactory.RegisterType("jsonencode", checkTypeExists); diff --git a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs index c1b9aa4..7e2cdd1 100644 --- a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs @@ -51,6 +51,7 @@ namespace NLog.LayoutRenderers.Wrappers [AmbientProperty(nameof(Cached))] [AmbientProperty(nameof(ClearCache))] [AmbientProperty(nameof(CachedSeconds))] + [AmbientProperty(nameof(CachedUntil))] [AppDomainFixedOutput] [ThreadAgnostic] public sealed class CachedLayoutRendererWrapper : WrapperLayoutRendererBase, IStringValueRenderer @@ -69,11 +70,26 @@ namespace NLog.LayoutRenderers.Wrappers OnClose = 2 } + /// + /// A value indicating when the cached value expires, because the time of the has moved into a new period. + /// + public enum CachedUntilOption + { + /// Never expire the cached value because of the time. + None = 0, + /// Expire the cached value when the day changes. + DayChange, + /// Expire the cached value when the hour changes. + HourChange + } + private readonly object _lockObject = new object(); private string? _cachedValue; private string? _renderedCacheKey; private DateTime _cachedValueExpires; private TimeSpan? _cachedValueTimeout; + private DateTime _cachedValuePeriodEnd; + private CachedUntilOption _cachedUntil; /// /// Gets or sets a value indicating whether this is enabled. @@ -112,6 +128,22 @@ namespace NLog.LayoutRenderers.Wrappers } } + /// + /// Gets or sets when the cached value should expire, so it is reset exactly when the hour or day of the LogEvent TimeStamp changes. Example HourChange will reset the cached value at the start of every hour. + /// + /// Default: + /// + public CachedUntilOption CachedUntil + { + get => _cachedUntil; + set + { + _cachedUntil = value; + if (_cachedUntil != CachedUntilOption.None) + Cached = true; + } + } + /// protected override void InitializeLayoutRenderer() { @@ -153,6 +185,8 @@ namespace NLog.LayoutRenderers.Wrappers _renderedCacheKey = newCacheKey; if (_cachedValueTimeout.HasValue) _cachedValueExpires = logEvent.TimeStamp + _cachedValueTimeout.Value; + if (_cachedUntil != CachedUntilOption.None) + _cachedValuePeriodEnd = CalculatePeriodEnd(_cachedUntil, logEvent.TimeStamp); } } } @@ -173,9 +207,22 @@ namespace NLog.LayoutRenderers.Wrappers if (_cachedValueTimeout.HasValue && logEvent.TimeStamp > _cachedValueExpires) return null; + if (_cachedUntil != CachedUntilOption.None && logEvent.TimeStamp >= _cachedValuePeriodEnd) + return null; + return _cachedValue; } + private static DateTime CalculatePeriodEnd(CachedUntilOption cachedUntil, DateTime timeStamp) + { + switch (cachedUntil) + { + case CachedUntilOption.DayChange: return new DateTime(timeStamp.Year, timeStamp.Month, timeStamp.Day, 0, 0, 0, timeStamp.Kind).AddDays(1); + case CachedUntilOption.HourChange: return new DateTime(timeStamp.Year, timeStamp.Month, timeStamp.Day, timeStamp.Hour, 0, 0, timeStamp.Kind).AddHours(1); + default: return DateTime.MaxValue; + } + } + 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 index ad401f7..752668c 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs @@ -35,6 +35,7 @@ using NLog.Config; namespace NLog.UnitTests.LayoutRenderers.Wrappers { + using System; using System.Linq; using NLog; using NLog.Layouts; @@ -127,5 +128,29 @@ namespace NLog.UnitTests.LayoutRenderers.Wrappers var s3 = l.Render(new LogEventInfo() { TimeStamp = NLog.Time.TimeSource.Current.Time.AddMinutes(2) }); Assert.NotEqual(s2, s3); } + + [Fact] + public void CachedUntilHourChangeTest() + { + SimpleLayout l = "${guid:cachedUntil=HourChange}"; + var timeStamp = new DateTime(2021, 2, 27, 17, 30, 0); + var s1 = l.Render(new LogEventInfo() { TimeStamp = timeStamp }); + var s2 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddMinutes(29) }); + Assert.Equal(s1, s2); + var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddMinutes(30) }); + Assert.NotEqual(s2, s3); + } + + [Fact] + public void CachedUntilDayChangeTest() + { + SimpleLayout l = "${guid:cachedUntil=DayChange}"; + var timeStamp = new DateTime(2021, 2, 27, 17, 30, 0); + var s1 = l.Render(new LogEventInfo() { TimeStamp = timeStamp }); + var s2 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6) }); + Assert.Equal(s1, s2); + var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6.5) }); + Assert.NotEqual(s2, s3); + } } }