Last active
April 3, 2019 08:27
-
-
Save joacar/81c9907a6077e8936bfb628804c3a643 to your computer and use it in GitHub Desktop.
EventId provider to retrieve event id from log calls made by `Microsoft.Extensions.Logging` and write to Windows Event Log
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <inheritdoc /> | |
/// <summary> | |
/// Extract the EventId.Id value from <see cref="Microsoft.Extensions.Logging.EventId" /> passed with | |
/// <see cref="Microsoft.Extensions.Logging" /> and set the <c>Event ID</c> field in Windows Event Log. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// Serilog creates an <see cref="Serilog.Events.LogEventPropertyValue" /> with two | |
/// <see cref="Serilog.Events.ScalarValue" /> combined into | |
/// <see cref="Serilog.Events.StructureValue" />. One <c>ScalarValue</c> has the actual id with the key <c>Id</c> and the | |
/// other the name | |
/// with the key <c>Name</c>. This method returns the <c>EventId.Id</c> value. | |
/// </para> | |
/// <para> | |
/// For details visit | |
/// https://github.com/serilog/serilog-sinks-eventlog/blob/dev/src/Serilog.Sinks.EventLog/Sinks/EventLog/EventLogSink.cs. | |
/// </para> | |
/// </remarks> | |
public class MicrosoftLoggerEventIdProvider : IEventIdProvider | |
{ | |
private readonly Func<string, ushort> _hashFunction = ComputeHash; | |
private readonly bool _throwIfEventIdNotPresent; | |
/// <summary> | |
/// Construct <see cref="MicrosoftLoggerEventIdProvider" /> with <paramref name="throwIfEventIdNotPresent" />. | |
/// </summary> | |
/// <param name="throwIfEventIdNotPresent">Throws an <see cref="InvalidOperationException" /></param> | |
public MicrosoftLoggerEventIdProvider(bool throwIfEventIdNotPresent) | |
{ | |
_throwIfEventIdNotPresent = throwIfEventIdNotPresent; | |
} | |
/// <summary> | |
/// Construct <see cref="MicrosoftLoggerEventIdProvider" /> with <paramref name="eventIdHashFunction" />. | |
/// </summary> | |
/// <param name="eventIdHashFunction"> | |
/// Function to calculate the event id written to event log. | |
/// </param> | |
public MicrosoftLoggerEventIdProvider(Func<string, ushort> eventIdHashFunction) : this(false) | |
{ | |
_hashFunction = eventIdHashFunction ?? throw new ArgumentNullException(nameof(eventIdHashFunction)); | |
} | |
/// <summary> | |
/// Construct <see cref="MicrosoftLoggerEventIdProvider" /> using default event id hash function if not <c>EventId.Id</c> | |
/// property is found in <see cref="LogEvent.Properties" />. | |
/// </summary> | |
public MicrosoftLoggerEventIdProvider() : this(false) | |
{ | |
} | |
/// <summary> | |
/// Get the numeric id for <c>EventId</c> property in <paramref name="logEvent" /> if present. Otherwise compute event id | |
/// using | |
/// message template text hash. | |
/// </summary> | |
/// <param name="logEvent">The <see cref="Serilog.Events.LogEvent" /> to inspect.</param> | |
/// <returns></returns> | |
public ushort ComputeEventId(LogEvent logEvent) | |
{ | |
// Check if the properties has StructureValue with key EventId | |
// passed from Microsoft.Extensions.Logging | |
if(!logEvent.Properties.TryGetValue("EventId", out var propertyValue) || | |
!(propertyValue is StructureValue structureValue)) | |
{ | |
if(_throwIfEventIdNotPresent) | |
{ | |
throw new InvalidOperationException("No value for key EventId was found"); | |
} | |
return _hashFunction(logEvent.MessageTemplate.Text); | |
} | |
// ReSharper disable once ForCanBeConvertedToForeach | |
// Don't allocate iterator. | |
// See here https://github.com/serilog/serilog-extensions-logging/blob/dev/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs | |
// for details how EventId property is constructed. | |
for(var i = 0; i < structureValue.Properties.Count; ++i) | |
{ | |
if(!"Id".Equals(structureValue.Properties[i].Name, StringComparison.Ordinal)) | |
{ | |
continue; | |
} | |
if(!(structureValue.Properties[i].Value is ScalarValue scalarValue)) | |
{ | |
continue; | |
} | |
var eventId = (ushort)(int)scalarValue.Value; | |
return eventId; | |
} | |
if(_throwIfEventIdNotPresent) | |
{ | |
throw new InvalidOperationException("No event id found for key Id"); | |
} | |
return _hashFunction(logEvent.MessageTemplate.Text); | |
} | |
/// <summary> | |
/// Compute a 32-bit hash of the provided <paramref name="messageTemplate" />. The | |
/// resulting hash value can be uses as an event id in lieu of transmitting the | |
/// full template string. | |
/// </summary> | |
/// <remarks> | |
/// https://github.com/serilog/serilog-sinks-eventlog/blob/dev/src/Serilog.Sinks.EventLog/Sinks/EventLog/EventIdHashProvider.cs#L39 | |
/// </remarks> | |
/// <param name="messageTemplate">A message template.</param> | |
/// <returns>A 32-bit hash of the template.</returns> | |
private static ushort ComputeHash(string messageTemplate) | |
{ | |
if(messageTemplate == null) | |
{ | |
throw new ArgumentNullException(nameof(messageTemplate)); | |
} | |
// Jenkins one-at-a-time https://en.wikipedia.org/wiki/Jenkins_hash_function | |
unchecked | |
{ | |
uint hash = 0; | |
// ReSharper disable once ForCanBeConvertedToForeach | |
for(var i = 0; i < messageTemplate.Length; ++i) | |
{ | |
hash += messageTemplate[i]; | |
hash += hash << 10; | |
hash ^= hash >> 6; | |
} | |
hash += hash << 3; | |
hash ^= hash >> 11; | |
hash += hash << 15; | |
//even though the api is type int, eventID must be between 0 and 65535 | |
//https://msdn.microsoft.com/en-us/library/d3159s0c(v=vs.110).aspx | |
return (ushort)hash; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment