CachedLayoutRendererWrapper - Added CachedUntil to reset cached value when hour or day changes

Fixes #4309
This commit is contained in:
dualfroz committed 2026-10-03 13:57:41 +02:00
1 parent 9fc32d87a6
commit 9b380aaa7d
3 files changed
+73

No files matched your search

@@ -172,6 +172,7 @@ namespace NLog.Config
ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("Cached", checkTypeExists); ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("Cached", checkTypeExists);
ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("ClearCache", checkTypeExists); ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("ClearCache", checkTypeExists);
ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("CachedSeconds", checkTypeExists); ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("CachedSeconds", checkTypeExists);
ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper>("CachedUntil", checkTypeExists);
layoutRendererFactory.RegisterType<NLog.LayoutRenderers.Wrappers.FileSystemNormalizeLayoutRendererWrapper>("filesystemnormalize", checkTypeExists); layoutRendererFactory.RegisterType<NLog.LayoutRenderers.Wrappers.FileSystemNormalizeLayoutRendererWrapper>("filesystemnormalize", checkTypeExists);
ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.FileSystemNormalizeLayoutRendererWrapper>("FSNormalize", checkTypeExists); ambientPropertyFactory.RegisterType<NLog.LayoutRenderers.Wrappers.FileSystemNormalizeLayoutRendererWrapper>("FSNormalize", checkTypeExists);
layoutRendererFactory.RegisterType<NLog.LayoutRenderers.Wrappers.JsonEncodeLayoutRendererWrapper>("jsonencode", checkTypeExists); layoutRendererFactory.RegisterType<NLog.LayoutRenderers.Wrappers.JsonEncodeLayoutRendererWrapper>("jsonencode", checkTypeExists);
@@ -51,6 +51,7 @@ namespace NLog.LayoutRenderers.Wrappers
[AmbientProperty(nameof(Cached))] [AmbientProperty(nameof(Cached))]
[AmbientProperty(nameof(ClearCache))] [AmbientProperty(nameof(ClearCache))]
[AmbientProperty(nameof(CachedSeconds))] [AmbientProperty(nameof(CachedSeconds))]
[AmbientProperty(nameof(CachedUntil))]
[AppDomainFixedOutput] [AppDomainFixedOutput]
[ThreadAgnostic] [ThreadAgnostic]
public sealed class CachedLayoutRendererWrapper : WrapperLayoutRendererBase, IStringValueRenderer public sealed class CachedLayoutRendererWrapper : WrapperLayoutRendererBase, IStringValueRenderer
@@ -69,11 +70,26 @@ namespace NLog.LayoutRenderers.Wrappers
OnClose = 2 OnClose = 2
} }
/// <summary>
/// A value indicating when the cached value expires, because the time of the <see cref="LogEventInfo"/> has moved into a new period.
/// </summary>
public enum CachedUntilOption
{
/// <summary>Never expire the cached value because of the time.</summary>
None = 0,
/// <summary>Expire the cached value when the day changes.</summary>
DayChange,
/// <summary>Expire the cached value when the hour changes.</summary>
HourChange
}
private readonly object _lockObject = new object(); private readonly object _lockObject = new object();
private string? _cachedValue; private string? _cachedValue;
private string? _renderedCacheKey; private string? _renderedCacheKey;
private DateTime _cachedValueExpires; private DateTime _cachedValueExpires;
private TimeSpan? _cachedValueTimeout; private TimeSpan? _cachedValueTimeout;
private DateTime _cachedValuePeriodEnd;
private CachedUntilOption _cachedUntil;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this <see cref="CachedLayoutRendererWrapper"/> is enabled. /// Gets or sets a value indicating whether this <see cref="CachedLayoutRendererWrapper"/> is enabled.
@@ -112,6 +128,22 @@ namespace NLog.LayoutRenderers.Wrappers
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <remarks>Default: <see cref="CachedUntilOption.None"/></remarks>
/// <docgen category='Layout Options' order='10' />
public CachedUntilOption CachedUntil
{
get => _cachedUntil;
set
{
_cachedUntil = value;
if (_cachedUntil != CachedUntilOption.None)
Cached = true;
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void InitializeLayoutRenderer() protected override void InitializeLayoutRenderer()
{ {
@@ -153,6 +185,8 @@ namespace NLog.LayoutRenderers.Wrappers
_renderedCacheKey = newCacheKey; _renderedCacheKey = newCacheKey;
if (_cachedValueTimeout.HasValue) if (_cachedValueTimeout.HasValue)
_cachedValueExpires = logEvent.TimeStamp + _cachedValueTimeout.Value; _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) if (_cachedValueTimeout.HasValue && logEvent.TimeStamp > _cachedValueExpires)
return null; return null;
if (_cachedUntil != CachedUntilOption.None && logEvent.TimeStamp >= _cachedValuePeriodEnd)
return null;
return _cachedValue; 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; string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null;
} }
} }
@@ -35,6 +35,7 @@ using NLog.Config;
namespace NLog.UnitTests.LayoutRenderers.Wrappers namespace NLog.UnitTests.LayoutRenderers.Wrappers
{ {
using System;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NLog.Layouts; 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) }); var s3 = l.Render(new LogEventInfo() { TimeStamp = NLog.Time.TimeSource.Current.Time.AddMinutes(2) });
Assert.NotEqual(s2, s3); 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);
}
} }
} }