-
-
Save samsp-msft/37a5aba3ef7abb5bbadf01ed48477886 to your computer and use it in GitHub Desktop.
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
using LogEnumerator; | |
using Microsoft.Extensions.DependencyInjection.Extensions; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
using System.Collections.Concurrent; | |
using System.Runtime.Versioning; | |
using Microsoft.Extensions.Logging.Configuration; | |
using System.Reflection.Metadata.Ecma335; | |
using System.Text; | |
namespace LogEnumerator | |
{ | |
public sealed class LogEnumeratorLoggerConfiguration | |
{ | |
public bool IsEnabled = true; | |
} | |
public sealed class LogEnumeratorLogger : ILogger | |
{ | |
private LogsPerCategory _myLogs; | |
private string _category; | |
public LogEnumeratorLogger(string category, LogsPerCategory myLogs, Func<LogEnumeratorLoggerConfiguration> _getCurrentConfig) | |
{ | |
_category = category; | |
_myLogs = myLogs; | |
} | |
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!; | |
public bool IsEnabled(LogLevel logLevel) => true; | |
public void Log<TState>( | |
LogLevel logLevel, | |
EventId eventId, | |
TState state, | |
Exception? exception, | |
Func<TState, Exception?, string> formatter) | |
{ | |
string? message = null; | |
if (state is IEnumerable<KeyValuePair<string, object>> props) | |
{ | |
foreach (var kvp in props) | |
{ | |
if (kvp.Key == "{OriginalFormat}") | |
{ | |
message = kvp.Value as string; | |
break; | |
} | |
} | |
} | |
Func<LogEntry> logEntryFactory = () => new LogEntry() | |
{ | |
Category = _category, | |
Level = logLevel, | |
EventID = eventId.Id, | |
MessageFormat = message, | |
Count = 0 | |
}; | |
if (eventId.Id == 0) | |
{ | |
if (message != null) | |
{ | |
var entry = _myLogs._OtherEntries.GetOrAdd(message, (name) => logEntryFactory()); | |
Interlocked.Increment(ref entry.Count); | |
} | |
} | |
else | |
{ | |
var entry = _myLogs._IDEntries.GetOrAdd(eventId.Id, (id) => logEntryFactory()); | |
Interlocked.Increment(ref entry.Count); | |
} | |
} | |
} | |
public class LogsPerCategory | |
{ | |
public ConcurrentDictionary<int, LogEntry> _IDEntries = new(); | |
public ConcurrentDictionary<string, LogEntry> _OtherEntries = new(); | |
} | |
public class LogEntry | |
{ | |
public string Category; | |
public LogLevel Level; | |
public int? EventID; | |
public string MessageFormat; | |
public int Count; | |
} | |
[UnsupportedOSPlatform("browser")] | |
[ProviderAlias("LogEnumerator")] | |
public sealed class LogEnumeratorLoggerProvider : ILoggerProvider | |
{ | |
private readonly IDisposable? _onChangeToken; | |
private LogEnumeratorLoggerConfiguration _currentConfig; | |
private static ConcurrentDictionary<string, LogsPerCategory> _logs = new(StringComparer.OrdinalIgnoreCase); | |
private readonly ConcurrentDictionary<string, LogEnumeratorLogger> _loggers = | |
new(StringComparer.OrdinalIgnoreCase); | |
public LogEnumeratorLoggerProvider( | |
IOptionsMonitor<LogEnumeratorLoggerConfiguration> config) | |
{ | |
_currentConfig = config.CurrentValue; | |
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig); | |
} | |
public ILogger CreateLogger(string categoryName) | |
{ | |
var logData = _logs.GetOrAdd(categoryName, name => new LogsPerCategory()); | |
var logger = _loggers.GetOrAdd(categoryName, name => new LogEnumeratorLogger(name, logData, GetCurrentConfig)); | |
return logger; | |
} | |
private LogEnumeratorLoggerConfiguration GetCurrentConfig() => _currentConfig; | |
public void Dispose() | |
{ | |
_loggers.Clear(); | |
_onChangeToken?.Dispose(); | |
} | |
public static string dumpLogInfo() | |
{ | |
StringBuilder stringBuilder = new StringBuilder(); | |
stringBuilder.AppendLine($"Category\tLevel\tID\tMessage Format\tCount"); | |
foreach (var provider in _logs) | |
{ | |
foreach (var entry in provider.Value._IDEntries.Values) | |
{ | |
stringBuilder.AppendLine($"{entry.Category}\t{entry.Level}\t{entry.EventID,2}\t{entry.MessageFormat}\t{entry.Count}"); | |
} | |
foreach (var entry in provider.Value._OtherEntries.Values) | |
{ | |
stringBuilder.AppendLine($"{entry.Category}\t{entry.Level}\t--\t{entry.MessageFormat}\t{entry.Count}"); | |
} | |
} | |
return stringBuilder.ToString(); | |
} | |
} | |
} | |
public static class LogEnumeratorLoggerExtensions | |
{ | |
public static ILoggingBuilder AddLogEnumeratorLogger( | |
this ILoggingBuilder builder) | |
{ | |
builder.AddConfiguration(); | |
builder.Services.TryAddEnumerable( | |
ServiceDescriptor.Singleton<ILoggerProvider, LogEnumeratorLoggerProvider>()); | |
LoggerProviderOptions.RegisterProviderOptions | |
<LogEnumeratorLoggerConfiguration, LogEnumeratorLoggerProvider>(builder.Services); | |
return builder; | |
} | |
public static ILoggingBuilder AddLogEnumeratorLogger( | |
this ILoggingBuilder builder, | |
Action<LogEnumeratorLoggerConfiguration> configure) | |
{ | |
builder.AddLogEnumeratorLogger(); | |
builder.Services.Configure(configure); | |
return builder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output for the markdown format