Compare commits
10
Commits
ce13d92371
...
d20e7c3697
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d20e7c3697 | ||
|
|
c07403dac0 | ||
|
|
eb43ff4c98 | ||
|
|
7c96e99fb3 | ||
|
|
13670749e5 | ||
|
|
9537db7a71 | ||
|
|
d490e07ea2 | ||
|
|
9b8ab6666e | ||
|
|
c9cd6bf57c | ||
|
|
97f1f71bbe |
No files matched your search
@@ -0,0 +1,11 @@
|
|||||||
|
# Contributions to NLog/NLog
|
||||||
|
|
||||||
|
NLog - Flexible and Structured Logging for various .NET Platforms
|
||||||
|
|
||||||
|
This repository holds only my commits to the upstream project, with their original author, date and diff.
|
||||||
|
Each of my commits is preceded by a snapshot of the upstream files it touched, so every diff shows exactly what I changed.
|
||||||
|
|
||||||
|
## Merged pull requests
|
||||||
|
|
||||||
|
- [CachedLayoutRendererWrapper - Added CachedUntil to reset cached value when hour or day changes](https://github.com/NLog/NLog/pull/6292) (2026-10-11)
|
||||||
|
- [docs: document default values for Filters properties in XML docs](https://github.com/NLog/NLog/pull/6275) (2026-09-05)
|
||||||
@@ -49,8 +49,6 @@ namespace NLog.Internal
|
|||||||
{
|
{
|
||||||
private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache;
|
private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache;
|
||||||
private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache;
|
private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache;
|
||||||
private MruCache<Type, ObjectPropertyInfos> ObjectTypeFieldCache => _objectTypeFieldCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeFieldCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeFieldCache;
|
|
||||||
private MruCache<Type, ObjectPropertyInfos>? _objectTypeFieldCache;
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this);
|
private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this);
|
||||||
private IObjectTypeTransformer? _objectTypeTransformation;
|
private IObjectTypeTransformer? _objectTypeTransformation;
|
||||||
@@ -68,7 +66,7 @@ namespace NLog.Internal
|
|||||||
public ObjectPropertyList LookupObjectProperties(object value) => LookupObjectProperties(value, false);
|
public ObjectPropertyList LookupObjectProperties(object value) => LookupObjectProperties(value, false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lookup object members, optionally including public fields besides public properties.
|
/// Lookup object properties, optionally falling back to public fields when the object has no public properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ObjectPropertyList LookupObjectProperties(object value, bool includeFields)
|
public ObjectPropertyList LookupObjectProperties(object value, bool includeFields)
|
||||||
{
|
{
|
||||||
@@ -97,13 +95,14 @@ namespace NLog.Internal
|
|||||||
}
|
}
|
||||||
|
|
||||||
var objectType = value.GetType();
|
var objectType = value.GetType();
|
||||||
var typeCache = includeFields ? ObjectTypeFieldCache : ObjectTypeCache;
|
var propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value, includeFields) : ObjectPropertyInfos.SimpleToString;
|
||||||
if (!typeCache.TryGetValue(objectType, out var propertyInfos))
|
if (includeFields && propertyInfos.FieldLookup is null)
|
||||||
{
|
{
|
||||||
propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value, includeFields) : ObjectPropertyInfos.SimpleToString;
|
// Remember that the public fields were resolved, so the type is not reflected again
|
||||||
typeCache.TryAddValue(objectType, propertyInfos);
|
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, propertyInfos.FastLookup, ArrayHelper.Empty<FastPropertyLookup>());
|
||||||
}
|
}
|
||||||
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
|
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
|
||||||
|
return propertyInfos.CreatePropertyList(value, includeFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -162,15 +161,15 @@ namespace NLog.Internal
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Type objectType = value.GetType();
|
Type objectType = value.GetType();
|
||||||
if (!includeFields && ObjectTypeCache.TryGetValue(objectType, out var propertyInfos))
|
if (ObjectTypeCache.TryGetValue(objectType, out var propertyInfos) && !(includeFields && propertyInfos.NeedsFieldLookup))
|
||||||
{
|
{
|
||||||
if (!propertyInfos.HasFastLookup)
|
if (!propertyInfos.HasFastLookup)
|
||||||
{
|
{
|
||||||
var fastLookup = BuildFastLookup(propertyInfos.Properties, false);
|
var fastLookup = BuildFastLookup(propertyInfos.Properties, false);
|
||||||
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup);
|
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup, propertyInfos.FieldLookup);
|
||||||
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
|
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
|
||||||
}
|
}
|
||||||
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
|
objectPropertyList = propertyInfos.CreatePropertyList(value, includeFields);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,15 +199,12 @@ namespace NLog.Internal
|
|||||||
}
|
}
|
||||||
else if (properties.Length == 0)
|
else if (properties.Length == 0)
|
||||||
{
|
{
|
||||||
// Nothing to show as properties, so fall back to the public fields when asked to.
|
if (!includeFields)
|
||||||
var fields = includeFields ? GetPublicFields(objectType) : ArrayHelper.Empty<FieldInfo>();
|
return ObjectPropertyInfos.SimpleToString;
|
||||||
if (fields.Length > 0)
|
|
||||||
{
|
|
||||||
// Fields are only reachable through the fast-lookup, so it cannot be built lazily
|
|
||||||
return new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), BuildFieldLookup(fields));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ObjectPropertyInfos.SimpleToString;
|
// Nothing to show as properties, so keep the public fields aside for the IncludePublicFields fallback
|
||||||
|
var fields = GetPublicFields(objectType);
|
||||||
|
return new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>(), BuildFieldLookup(fields));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -626,20 +622,32 @@ namespace NLog.Internal
|
|||||||
{
|
{
|
||||||
public readonly PropertyInfo[] Properties;
|
public readonly PropertyInfo[] Properties;
|
||||||
public readonly FastPropertyLookup[]? FastLookup;
|
public readonly FastPropertyLookup[]? FastLookup;
|
||||||
|
/// <summary>Public fields of a type without public properties, null until resolved</summary>
|
||||||
|
public readonly FastPropertyLookup[]? FieldLookup;
|
||||||
|
|
||||||
public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>());
|
public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>());
|
||||||
|
|
||||||
public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
|
public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup, FastPropertyLookup[]? fieldLookup = null)
|
||||||
{
|
{
|
||||||
Properties = properties;
|
Properties = properties;
|
||||||
FastLookup = fastLookup;
|
FastLookup = fastLookup;
|
||||||
|
FieldLookup = fieldLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasFastLookup => FastLookup != null;
|
public bool HasFastLookup => FastLookup != null;
|
||||||
|
|
||||||
|
public bool NeedsFieldLookup => FieldLookup is null && Properties.Length == 0 && FastLookup?.Length == 0;
|
||||||
|
|
||||||
|
public ObjectPropertyList CreatePropertyList(object value, bool includeFields)
|
||||||
|
{
|
||||||
|
if (includeFields && FieldLookup?.Length > 0)
|
||||||
|
return new ObjectPropertyList(value, Properties, FieldLookup);
|
||||||
|
return new ObjectPropertyList(value, Properties, FastLookup);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(ObjectPropertyInfos other)
|
public bool Equals(ObjectPropertyInfos other)
|
||||||
{
|
{
|
||||||
return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length;
|
return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length && ReferenceEquals(FieldLookup, other.FieldLookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace NLog.LayoutRenderers.Wrappers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A value indicating when the cached value expires, because the time of the <see cref="LogEventInfo"/> has moved into a new period.
|
/// A value indicating when the cached value expires, because the time of the <see cref="LogEventInfo"/> has moved into a new period.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum CachedUntilOption
|
public enum CachedUntilLifetime
|
||||||
{
|
{
|
||||||
/// <summary>Never expire the cached value because of the time.</summary>
|
/// <summary>Never expire the cached value because of the time.</summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
@@ -86,11 +86,9 @@ namespace NLog.LayoutRenderers.Wrappers
|
|||||||
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 _cachedValuePeriodStart;
|
private CachedUntilLifetime _cachedUntil;
|
||||||
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.
|
||||||
@@ -132,15 +130,15 @@ namespace NLog.LayoutRenderers.Wrappers
|
|||||||
/// <summary>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
/// <remarks>Default: <see cref="CachedUntilOption.None"/></remarks>
|
/// <remarks>Default: <see cref="CachedUntilLifetime.None"/></remarks>
|
||||||
/// <docgen category='Layout Options' order='10' />
|
/// <docgen category='Layout Options' order='10' />
|
||||||
public CachedUntilOption CachedUntil
|
public CachedUntilLifetime CachedUntil
|
||||||
{
|
{
|
||||||
get => _cachedUntil;
|
get => _cachedUntil;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_cachedUntil = value;
|
_cachedUntil = value;
|
||||||
if (_cachedUntil != CachedUntilOption.None)
|
if (_cachedUntil != CachedUntilLifetime.None)
|
||||||
Cached = true;
|
Cached = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,10 +182,7 @@ namespace NLog.LayoutRenderers.Wrappers
|
|||||||
{
|
{
|
||||||
_cachedValue = cachedValue = base.RenderInner(logEvent);
|
_cachedValue = cachedValue = base.RenderInner(logEvent);
|
||||||
_renderedCacheKey = newCacheKey;
|
_renderedCacheKey = newCacheKey;
|
||||||
if (_cachedValueTimeout.HasValue)
|
_cachedValueExpires = ResolveCachedValueExpires(logEvent.TimeStamp);
|
||||||
_cachedValueExpires = logEvent.TimeStamp + _cachedValueTimeout.Value;
|
|
||||||
if (_cachedUntil != CachedUntilOption.None)
|
|
||||||
UpdateCachedValuePeriod(logEvent.TimeStamp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,21 +200,30 @@ namespace NLog.LayoutRenderers.Wrappers
|
|||||||
if (_renderedCacheKey != newCacheKey)
|
if (_renderedCacheKey != newCacheKey)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (_cachedValueTimeout.HasValue && logEvent.TimeStamp > _cachedValueExpires)
|
if (_cachedValueExpires.HasValue && logEvent.TimeStamp > _cachedValueExpires.Value)
|
||||||
return null;
|
|
||||||
|
|
||||||
if (_cachedUntil != CachedUntilOption.None && (logEvent.TimeStamp < _cachedValuePeriodStart || logEvent.TimeStamp > _cachedValuePeriodEnd))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return _cachedValue;
|
return _cachedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCachedValuePeriod(DateTime timeStamp)
|
private DateTime? ResolveCachedValueExpires(DateTime timeStamp)
|
||||||
{
|
{
|
||||||
var periodTicks = _cachedUntil == CachedUntilOption.DayChange ? TimeSpan.TicksPerDay : TimeSpan.TicksPerHour;
|
DateTime? expires = null;
|
||||||
_cachedValuePeriodStart = new DateTime(timeStamp.Ticks - timeStamp.Ticks % periodTicks, timeStamp.Kind);
|
if (_cachedValueTimeout.HasValue)
|
||||||
// Inclusive end, so the last period ends at DateTime.MaxValue without overflow
|
{
|
||||||
_cachedValuePeriodEnd = _cachedValuePeriodStart.AddTicks(periodTicks - 1);
|
var timeoutTicks = Math.Min(_cachedValueTimeout.Value.Ticks, DateTime.MaxValue.Ticks - timeStamp.Ticks);
|
||||||
|
expires = timeStamp.AddTicks(timeoutTicks);
|
||||||
|
}
|
||||||
|
if (_cachedUntil != CachedUntilLifetime.None)
|
||||||
|
{
|
||||||
|
var periodTicks = _cachedUntil == CachedUntilLifetime.DayChange ? TimeSpan.TicksPerDay : TimeSpan.TicksPerHour;
|
||||||
|
var periodStart = new DateTime(timeStamp.Ticks - timeStamp.Ticks % periodTicks, timeStamp.Kind);
|
||||||
|
// Inclusive end, so the last period ends at DateTime.MaxValue without overflow
|
||||||
|
var periodEnd = periodStart.AddTicks(periodTicks - 1);
|
||||||
|
if (!expires.HasValue || periodEnd < expires.Value)
|
||||||
|
expires = periodEnd;
|
||||||
|
}
|
||||||
|
return expires;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null;
|
string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => Cached ? RenderInner(logEvent) : null;
|
||||||
|
|||||||
@@ -152,45 +152,5 @@ namespace NLog.UnitTests.LayoutRenderers.Wrappers
|
|||||||
var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6.5) });
|
var s3 = l.Render(new LogEventInfo() { TimeStamp = timeStamp.AddHours(6.5) });
|
||||||
Assert.NotEqual(s2, s3);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +246,24 @@ namespace NLog.UnitTests.Targets
|
|||||||
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString());
|
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IncludePublicFields_SameTypeWithoutOptionFirst_StillSerializesFieldsLater()
|
||||||
|
{
|
||||||
|
var testObject = new ClassWithOnlyFields { Name = "John", Age = 42 };
|
||||||
|
var jsonSerializer = new DefaultJsonSerializer(null);
|
||||||
|
|
||||||
|
var withoutFields = new StringBuilder();
|
||||||
|
jsonSerializer.SerializeObject(testObject, withoutFields, new JsonSerializeOptions { IncludePublicFields = false });
|
||||||
|
var withFields = new StringBuilder();
|
||||||
|
jsonSerializer.SerializeObject(testObject, withFields, new JsonSerializeOptions { IncludePublicFields = true });
|
||||||
|
var withoutFieldsAgain = new StringBuilder();
|
||||||
|
jsonSerializer.SerializeObject(testObject, withoutFieldsAgain, new JsonSerializeOptions { IncludePublicFields = false });
|
||||||
|
|
||||||
|
Assert.DoesNotContain(@"""Age"":42", withoutFields.ToString());
|
||||||
|
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFields.ToString());
|
||||||
|
Assert.Equal(withoutFields.ToString(), withoutFieldsAgain.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IncludePublicFields_ShadowedField_IsWrittenOnce()
|
public void IncludePublicFields_ShadowedField_IsWrittenOnce()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user