/src/Gelf.Extensions.Logging/GelfLogger.cs

https://github.com/mattwcole/gelf-extensions-logging · C# · 196 lines · 176 code · 20 blank · 0 comment · 12 complexity · c9d1c9e7ad61644e05a66e2af9a7cf65 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text.RegularExpressions;
  6. using Microsoft.Extensions.Logging;
  7. using Microsoft.Extensions.Logging.Abstractions.Internal;
  8. namespace Gelf.Extensions.Logging
  9. {
  10. public class GelfLogger : ILogger
  11. {
  12. private static readonly Regex AdditionalFieldKeyRegex = new(@"^[\w\.\-]*$", RegexOptions.Compiled);
  13. private static readonly HashSet<string> ReservedAdditionalFieldKeys = new()
  14. {
  15. "id",
  16. "logger",
  17. "exception",
  18. "event_id",
  19. "event_name",
  20. "message_template"
  21. };
  22. private readonly string _name;
  23. private readonly GelfMessageProcessor _messageProcessor;
  24. private readonly GelfLoggerOptions _options;
  25. public GelfLogger(string name, GelfMessageProcessor messageProcessor, GelfLoggerOptions options)
  26. {
  27. _name = name;
  28. _messageProcessor = messageProcessor;
  29. _options = options;
  30. }
  31. internal IExternalScopeProvider? ScopeProvider { get; set; }
  32. public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
  33. Exception? exception, Func<TState, Exception?, string> formatter)
  34. {
  35. if (!IsEnabled(logLevel))
  36. {
  37. return;
  38. }
  39. var message = new GelfMessage
  40. {
  41. ShortMessage = formatter(state, exception),
  42. Host = _options.LogSource,
  43. Logger = _name,
  44. Exception = exception?.ToString(),
  45. Level = GetLevel(logLevel),
  46. Timestamp = GetTimestamp(),
  47. AdditionalFields = GetAdditionalFields(logLevel, eventId, state, exception).ToArray()
  48. };
  49. if (eventId != default)
  50. {
  51. message.EventId = eventId.Id;
  52. message.EventName = eventId.Name;
  53. }
  54. _messageProcessor.SendMessage(message);
  55. }
  56. public bool IsEnabled(LogLevel logLevel)
  57. {
  58. return logLevel != LogLevel.None;
  59. }
  60. public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;
  61. private static SyslogSeverity GetLevel(LogLevel logLevel)
  62. {
  63. return logLevel switch
  64. {
  65. LogLevel.Trace => SyslogSeverity.Debug,
  66. LogLevel.Debug => SyslogSeverity.Debug,
  67. LogLevel.Information => SyslogSeverity.Informational,
  68. LogLevel.Warning => SyslogSeverity.Warning,
  69. LogLevel.Error => SyslogSeverity.Error,
  70. LogLevel.Critical => SyslogSeverity.Critical,
  71. _ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, "Log level not supported.")
  72. };
  73. }
  74. private static double GetTimestamp()
  75. {
  76. var totalMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  77. var totalSeconds = totalMilliseconds / 1000d;
  78. return Math.Round(totalSeconds, 3);
  79. }
  80. private IEnumerable<KeyValuePair<string, object>> GetAdditionalFields<TState>(
  81. LogLevel logLevel, EventId eventId, TState state, Exception? exception)
  82. {
  83. var additionalFields = _options.AdditionalFields
  84. .Concat(GetFactoryAdditionalFields(logLevel, eventId, exception))
  85. .Concat(GetScopeAdditionalFields())
  86. .Concat(GetStateAdditionalFields(state));
  87. foreach (var field in additionalFields)
  88. {
  89. if (field.Key != "{OriginalFormat}")
  90. {
  91. if (AdditionalFieldKeyRegex.IsMatch(field.Key) && !ReservedAdditionalFieldKeys.Contains(field.Key))
  92. {
  93. yield return field;
  94. }
  95. else
  96. {
  97. Debug.Fail($"GELF message has additional field with invalid key \"{field.Key}\".");
  98. }
  99. }
  100. else if (_options.IncludeMessageTemplates)
  101. {
  102. yield return new KeyValuePair<string, object>("message_template", field.Value);
  103. }
  104. }
  105. }
  106. private IEnumerable<KeyValuePair<string, object>> GetFactoryAdditionalFields(
  107. LogLevel logLevel, EventId eventId, Exception? exception)
  108. {
  109. return _options.AdditionalFieldsFactory?.Invoke(logLevel, eventId, exception) ??
  110. Enumerable.Empty<KeyValuePair<string, object>>();
  111. }
  112. private IEnumerable<KeyValuePair<string, object>> GetScopeAdditionalFields()
  113. {
  114. if (!_options.IncludeScopes)
  115. {
  116. return Enumerable.Empty<KeyValuePair<string, object>>();
  117. }
  118. var additionalFields = new List<KeyValuePair<string, object>>();
  119. ScopeProvider?.ForEachScope((scope, state) =>
  120. {
  121. switch (scope)
  122. {
  123. case IEnumerable<KeyValuePair<string, object>> fields:
  124. state.AddRange(fields);
  125. break;
  126. case ValueTuple<string, string>(var key, var value):
  127. state.Add(new KeyValuePair<string, object>(key, value));
  128. break;
  129. case ValueTuple<string, int>(var key, var value):
  130. state.Add(new KeyValuePair<string, object>(key, value));
  131. break;
  132. case ValueTuple<string, long>(var key, var value):
  133. state.Add(new KeyValuePair<string, object>(key, value));
  134. break;
  135. case ValueTuple<string, short>(var key, var value):
  136. state.Add(new KeyValuePair<string, object>(key, value));
  137. break;
  138. case ValueTuple<string, decimal>(var key, var value):
  139. state.Add(new KeyValuePair<string, object>(key, value));
  140. break;
  141. case ValueTuple<string, double>(var key, var value):
  142. state.Add(new KeyValuePair<string, object>(key, value));
  143. break;
  144. case ValueTuple<string, float>(var key, var value):
  145. state.Add(new KeyValuePair<string, object>(key, value));
  146. break;
  147. case ValueTuple<string, uint>(var key, var value):
  148. state.Add(new KeyValuePair<string, object>(key, value));
  149. break;
  150. case ValueTuple<string, ulong>(var key, var value):
  151. state.Add(new KeyValuePair<string, object>(key, value));
  152. break;
  153. case ValueTuple<string, ushort>(var key, var value):
  154. state.Add(new KeyValuePair<string, object>(key, value));
  155. break;
  156. case ValueTuple<string, byte>(var key, var value):
  157. state.Add(new KeyValuePair<string, object>(key, value));
  158. break;
  159. case ValueTuple<string, sbyte>(var key, var value):
  160. state.Add(new KeyValuePair<string, object>(key, value));
  161. break;
  162. case ValueTuple<string, object>(var key, var value):
  163. state.Add(new KeyValuePair<string, object>(key, value));
  164. break;
  165. }
  166. }, additionalFields);
  167. return additionalFields;
  168. }
  169. private static IEnumerable<KeyValuePair<string, object>> GetStateAdditionalFields<TState>(TState state)
  170. {
  171. return state is IEnumerable<KeyValuePair<string, object>> additionalFields
  172. ? additionalFields
  173. : Enumerable.Empty<KeyValuePair<string, object>>();
  174. }
  175. }
  176. }