From ce13d92371f3913746a0ff8cbbf204e1779cc2aa Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 3 Oct 2026 14:06:18 +0200 Subject: [PATCH] CachedUntil - Only reuse cached value within its own period, and no overflow for the last period --- .../Wrappers/CachedLayoutRendererWrapper.cs | 17 ++++---- .../LayoutRenderers/Wrappers/CachedTests.cs | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs index 7e2cdd1..d6cb07e 100644 --- a/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs @@ -88,6 +88,7 @@ namespace NLog.LayoutRenderers.Wrappers private string? _renderedCacheKey; private DateTime _cachedValueExpires; private TimeSpan? _cachedValueTimeout; + private DateTime _cachedValuePeriodStart; private DateTime _cachedValuePeriodEnd; private CachedUntilOption _cachedUntil; @@ -186,7 +187,7 @@ namespace NLog.LayoutRenderers.Wrappers if (_cachedValueTimeout.HasValue) _cachedValueExpires = logEvent.TimeStamp + _cachedValueTimeout.Value; if (_cachedUntil != CachedUntilOption.None) - _cachedValuePeriodEnd = CalculatePeriodEnd(_cachedUntil, logEvent.TimeStamp); + UpdateCachedValuePeriod(logEvent.TimeStamp); } } } @@ -207,20 +208,18 @@ namespace NLog.LayoutRenderers.Wrappers if (_cachedValueTimeout.HasValue && logEvent.TimeStamp > _cachedValueExpires) return null; - if (_cachedUntil != CachedUntilOption.None && logEvent.TimeStamp >= _cachedValuePeriodEnd) + if (_cachedUntil != CachedUntilOption.None && (logEvent.TimeStamp < _cachedValuePeriodStart || logEvent.TimeStamp > _cachedValuePeriodEnd)) return null; return _cachedValue; } - private static DateTime CalculatePeriodEnd(CachedUntilOption cachedUntil, DateTime timeStamp) + private void UpdateCachedValuePeriod(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; - } + var periodTicks = _cachedUntil == CachedUntilOption.DayChange ? TimeSpan.TicksPerDay : TimeSpan.TicksPerHour; + _cachedValuePeriodStart = new DateTime(timeStamp.Ticks - timeStamp.Ticks % periodTicks, timeStamp.Kind); + // Inclusive end, so the last period ends at DateTime.MaxValue without overflow + _cachedValuePeriodEnd = _cachedValuePeriodStart.AddTicks(periodTicks - 1); } 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 752668c..66a7661 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/CachedTests.cs @@ -152,5 +152,45 @@ namespace NLog.UnitTests.LayoutRenderers.Wrappers var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6.5) }); Assert.NotEqual(s2, s3); } + + [Fact] + public void CachedUntilHourChangeEarlierTimeStampTest() + { + 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(30) }); + Assert.NotEqual(s1, s2); + var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddMinutes(15) }); + Assert.NotEqual(s2, s3); + var s4 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddMinutes(-30) }); + Assert.Equal(s3, s4); + } + + [Fact] + public void CachedUntilDayChangeEarlierTimeStampTest() + { + 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(7) }); + Assert.NotEqual(s1, s2); + var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6) }); + Assert.NotEqual(s2, s3); + var s4 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(-17.5) }); + Assert.Equal(s3, s4); + } + + [Theory] + [InlineData("HourChange")] + [InlineData("DayChange")] + public void CachedUntilLastPeriodTest(string cachedUntil) + { + SimpleLayout l = "${guid:cachedUntil=" + cachedUntil + "}"; + var s1 = l.Render(new LogEventInfo() { TimeStamp = DateTime.MaxValue.AddMinutes(-30) }); + var s2 = l.Render(new LogEventInfo() { TimeStamp = DateTime.MaxValue }); + Assert.NotEmpty(s1); + Assert.Equal(s1, s2); + } } }