Skip to content

Instantly share code, notes, and snippets.

@joacar
Last active April 3, 2019 08:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joacar/81c9907a6077e8936bfb628804c3a643 to your computer and use it in GitHub Desktop.
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
/// <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