-
Notifications
You must be signed in to change notification settings - Fork 103
Size based EventId LogEventProperty cache #260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
d0dbbb1
0e5fe4c
4eb8b3c
919b919
b68c884
3731bcb
59165d5
cb77647
29b94f3
2bc0ea5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Serilog.Extensions.Logging | ||
AndreReise marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
using Microsoft.Extensions.Logging; | ||
using Serilog.Events; | ||
|
||
internal sealed class EventIdPropertyCache | ||
AndreReise marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly object _createLock = new(); | ||
private readonly int _maxCapacity; | ||
private readonly Dictionary<EventKey, LogEventProperty> _propertyCache; | ||
|
||
private int count; | ||
|
||
public EventIdPropertyCache(int maxCapacity) | ||
{ | ||
_maxCapacity = maxCapacity; | ||
_propertyCache = new Dictionary<EventKey, LogEventProperty>(capacity: maxCapacity); | ||
} | ||
|
||
public LogEventProperty GetOrCreateProperty(in EventId eventId) | ||
{ | ||
var eventKey = new EventKey(eventId); | ||
|
||
if (_propertyCache.TryGetValue(eventKey, out var cachedProperty)) | ||
AndreReise marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
return cachedProperty; | ||
} | ||
|
||
lock (_createLock) | ||
{ | ||
// Double check under lock | ||
if (_propertyCache.TryGetValue(eventKey, out cachedProperty)) | ||
{ | ||
return cachedProperty; | ||
} | ||
|
||
cachedProperty = CreateCore(in eventKey); | ||
|
||
if (count < _maxCapacity) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applications log a lot of things during start-up that they never log again. Perhaps clearing the cache when it reaches the size limit, and then (lazily) repopulating it, is worth considering, here? Bumping the cache size up to e.g. 256 items would help to avoid degenerate cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could consider making this cache shared (as a static class) among all loggers. Since
This problem can be addressed by choosing the correct max-cached-items parameter. Speculatively, the total set of application and library event IDs can typically fit into a single cache. A maximum of 1024 items seems reasonable to me. Implementing an LRU cache could help save some memory. However, this approach might introduce more lock contention, potentially impacting performance. So, I'd like to suggest using the approach the Microsoft team implemented for caching in FormattedLogValues. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Almost everything exactly the same as in FormattedLogValues source code. private const int MaxCachedProperties = 1024;
private static readonly ConcurrentDictionary<EventKey, LogEventProperty> s_propertyCache = new ();
private static int s_count;
public static LogEventProperty GetOrCreateProperty(in EventId eventId)
{
var eventKey = new EventKey(eventId);
LogEventProperty? property;
if (s_count >= MaxCachedProperties)
{
if (!s_propertyCache.TryGetValue(eventKey, out property))
{
property = CreateCore(in eventKey);
}
}
else
{
property = s_propertyCache.GetOrAdd(
eventKey,
static key =>
{
Interlocked.Increment(ref s_count);
return CreateCore(in key);
});
}
return property;
} Actually, the factory in |
||
{ | ||
_propertyCache[eventKey] = cachedProperty; | ||
count++; | ||
} | ||
|
||
return cachedProperty; | ||
} | ||
} | ||
|
||
private static LogEventProperty CreateCore(in EventKey eventKey) | ||
{ | ||
var properties = new List<LogEventProperty>(2); | ||
|
||
if (eventKey.Id != 0) | ||
{ | ||
properties.Add(new LogEventProperty("Id", new ScalarValue(eventKey.Id))); | ||
} | ||
|
||
if (eventKey.Name != null) | ||
{ | ||
properties.Add(new LogEventProperty("Name", new ScalarValue(eventKey.Name))); | ||
} | ||
|
||
return new LogEventProperty("EventId", new StructureValue(properties)); | ||
} | ||
|
||
private readonly struct EventKey : IEquatable<EventKey> | ||
AndreReise marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
|
||
public EventKey(EventId eventId) | ||
{ | ||
Id = eventId.Id; | ||
Name = eventId.Name; | ||
} | ||
|
||
public int Id { get; } | ||
|
||
public string? Name { get; } | ||
|
||
/// <inheritdoc /> | ||
public override int GetHashCode() | ||
{ | ||
unchecked | ||
{ | ||
var hashCode = 17; | ||
|
||
hashCode = (hashCode * 397) ^ this.Id; | ||
hashCode = (hashCode * 397) ^ (this.Name?.GetHashCode() ?? 0); | ||
|
||
return hashCode; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool Equals(EventKey other) => this.Id == other.Id && this.Name == other.Name; | ||
|
||
/// <inheritdoc /> | ||
public override bool Equals(object? obj) => obj is EventKey other && Equals(other); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using Microsoft.Extensions.Logging; | ||
using Serilog.Events; | ||
using Xunit; | ||
|
||
namespace Serilog.Extensions.Logging.Tests | ||
{ | ||
public class EventIdPropertyCacheTests | ||
{ | ||
[Theory] | ||
[InlineData(1)] | ||
[InlineData(10)] | ||
[InlineData(48)] | ||
[InlineData(100)] | ||
public void LowAndHighNumberedEventIdsAreMapped(int id) | ||
{ | ||
// Arrange | ||
var cache = new EventIdPropertyCache(48); | ||
|
||
var eventId = new EventId(id, "test"); | ||
|
||
// Act | ||
var mapped = cache.GetOrCreateProperty(eventId); | ||
|
||
// Assert | ||
var value = Assert.IsType<StructureValue>(mapped.Value); | ||
Assert.Equal(2, value.Properties.Count); | ||
|
||
var idValue = value.Properties.Single(p => p.Name == "Id").Value; | ||
var scalar = Assert.IsType<ScalarValue>(idValue); | ||
Assert.Equal(id, scalar.Value); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.