Created
January 8, 2020 21:58
-
-
Save TheXenocide/f7d8a249ec19857dee926426e2319746 to your computer and use it in GitHub Desktop.
StructuredLoggerExtensions
This file contains hidden or 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
/// <summary> | |
/// ILogger extension methods for common scenarios. | |
/// </summary> | |
public static class LoggerExtensions | |
{ | |
private static readonly Func<StructuredAndFormattedLogValues, Exception, string> _messageFormatter = MessageFormatter; | |
//------------------------------------------DEBUG------------------------------------------// | |
/// <summary> | |
/// Formats and writes a debug log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogDebug(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogDebug(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Debug, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a debug log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogDebug(0, "Processing request from {Address}", address)</example> | |
public static void LogDebug(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Debug, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a debug log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogDebug(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogDebug(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Debug, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a debug log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogDebug("Processing request from {Address}", address)</example> | |
public static void LogDebug(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Debug, additionalProperties, message, args); | |
} | |
//------------------------------------------TRACE------------------------------------------// | |
/// <summary> | |
/// Formats and writes a trace log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogTrace(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogTrace(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Trace, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a trace log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogTrace(0, "Processing request from {Address}", address)</example> | |
public static void LogTrace(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Trace, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a trace log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogTrace(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogTrace(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Trace, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a trace log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogTrace("Processing request from {Address}", address)</example> | |
public static void LogTrace(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Trace, additionalProperties, message, args); | |
} | |
//------------------------------------------INFORMATION------------------------------------------// | |
/// <summary> | |
/// Formats and writes an informational log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogInformation(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogInformation(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Information, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an informational log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogInformation(0, "Processing request from {Address}", address)</example> | |
public static void LogInformation(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Information, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an informational log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogInformation(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogInformation(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Information, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an informational log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogInformation("Processing request from {Address}", address)</example> | |
public static void LogInformation(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Information, additionalProperties, message, args); | |
} | |
//------------------------------------------WARNING------------------------------------------// | |
/// <summary> | |
/// Formats and writes a warning log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogWarning(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogWarning(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Warning, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a warning log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogWarning(0, "Processing request from {Address}", address)</example> | |
public static void LogWarning(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Warning, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a warning log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogWarning(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogWarning(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Warning, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a warning log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogWarning("Processing request from {Address}", address)</example> | |
public static void LogWarning(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Warning, additionalProperties, message, args); | |
} | |
//------------------------------------------ERROR------------------------------------------// | |
/// <summary> | |
/// Formats and writes an error log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogError(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogError(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Error, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an error log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogError(0, "Processing request from {Address}", address)</example> | |
public static void LogError(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Error, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an error log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogError(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogError(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Error, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes an error log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogError("Processing request from {Address}", address)</example> | |
public static void LogError(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Error, additionalProperties, message, args); | |
} | |
//------------------------------------------CRITICAL------------------------------------------// | |
/// <summary> | |
/// Formats and writes a critical log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogCritical(0, exception, "Error while processing request from {Address}", address)</example> | |
public static void LogCritical(this ILogger logger, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Critical, eventId, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a critical log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogCritical(0, "Processing request from {Address}", address)</example> | |
public static void LogCritical(this ILogger logger, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Critical, eventId, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a critical log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogCritical(exception, "Error while processing request from {Address}", address)</example> | |
public static void LogCritical(this ILogger logger, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Critical, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a critical log message. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="message">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <example>logger.LogCritical("Processing request from {Address}", address)</example> | |
public static void LogCritical(this ILogger logger, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(LogLevel.Critical, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a log message at the specified log level. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="logLevel">Entry will be written on this level.</param> | |
/// <param name="message">Format string of the log message.</param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
public static void Log(this ILogger logger, LogLevel logLevel, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(logLevel, 0, null, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a log message at the specified log level. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="logLevel">Entry will be written on this level.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="message">Format string of the log message.</param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(logLevel, eventId, null, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a log message at the specified log level. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="logLevel">Entry will be written on this level.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message.</param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
public static void Log(this ILogger logger, LogLevel logLevel, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
logger.Log(logLevel, 0, exception, additionalProperties, message, args); | |
} | |
/// <summary> | |
/// Formats and writes a log message at the specified log level. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to write to.</param> | |
/// <param name="logLevel">Entry will be written on this level.</param> | |
/// <param name="eventId">The event id associated with the log.</param> | |
/// <param name="exception">The exception to log.</param> | |
/// <param name="message">Format string of the log message.</param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string message, params object[] args) | |
{ | |
if (logger == null) | |
{ | |
throw new ArgumentNullException(nameof(logger)); | |
} | |
logger.Log(logLevel, eventId, new StructuredAndFormattedLogValues(additionalProperties, message, args), exception, _messageFormatter); | |
} | |
//------------------------------------------Scope------------------------------------------// | |
/// <summary> | |
/// Formats the message and creates a scope. | |
/// </summary> | |
/// <param name="logger">The <see cref="ILogger"/> to create the scope in.</param> | |
/// <param name="messageFormat">Format string of the log message in message template format. Example: <code>"User {User} logged in from {Address}"</code></param> | |
/// <param name="args">An object array that contains zero or more objects to format.</param> | |
/// <returns>A disposable scope object. Can be null.</returns> | |
/// <example> | |
/// using(logger.BeginScope("Processing request from {Address}", address)) | |
/// { | |
/// } | |
/// </example> | |
public static IDisposable BeginScope( | |
this ILogger logger, | |
IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, | |
string messageFormat, | |
params object[] args) | |
{ | |
if (logger == null) | |
{ | |
throw new ArgumentNullException(nameof(logger)); | |
} | |
return logger.BeginScope(new StructuredAndFormattedLogValues(additionalProperties, messageFormat, args)); | |
} | |
//------------------------------------------HELPERS------------------------------------------// | |
private static string MessageFormatter(StructuredAndFormattedLogValues state, Exception error) | |
{ | |
return state.ToString(); | |
} | |
// Define other methods, classes and namespaces here | |
/// <summary> | |
/// LogValues to enable formatting options supported by <see cref="M:string.Format"/>. | |
/// This also enables using {NamedformatItem} in the format string. | |
/// </summary> | |
readonly struct StructuredAndFormattedLogValues : IReadOnlyList<KeyValuePair<string, object>> | |
{ | |
internal const int MaxCachedFormatters = 1024; | |
private const string NullFormat = "[null]"; | |
private static int _count; | |
private static ConcurrentDictionary<string, CustomLogValuesFormatter> _formatters = new ConcurrentDictionary<string, CustomLogValuesFormatter>(); | |
private readonly CustomLogValuesFormatter _formatter; | |
private readonly object[] _values; | |
private readonly string _originalMessage; | |
private readonly IReadOnlyCollection<KeyValuePair<string, object>> _additionalProperties; | |
// for testing purposes | |
internal CustomLogValuesFormatter Formatter => _formatter; | |
public StructuredAndFormattedLogValues(IReadOnlyCollection<KeyValuePair<string, object>> additionalProperties, string format, params object[] values) | |
{ | |
_additionalProperties = additionalProperties; | |
if (values != null && values.Length != 0 && format != null) | |
{ | |
if (_count >= MaxCachedFormatters) | |
{ | |
if (!_formatters.TryGetValue(format, out _formatter)) | |
{ | |
_formatter = new CustomLogValuesFormatter(format); | |
} | |
} | |
else | |
{ | |
_formatter = _formatters.GetOrAdd(format, f => | |
{ | |
Interlocked.Increment(ref _count); | |
return new CustomLogValuesFormatter(f); | |
}); | |
} | |
} | |
else | |
{ | |
_formatter = null; | |
} | |
_originalMessage = format ?? NullFormat; | |
_values = values; | |
} | |
public KeyValuePair<string, object> this[int index] | |
{ | |
get | |
{ | |
if (index < 0 || index >= Count) | |
{ | |
throw new IndexOutOfRangeException(nameof(index)); | |
} | |
if (index == Count - 1) | |
{ | |
return new KeyValuePair<string, object>("{OriginalFormat}", _originalMessage); | |
} | |
else | |
{ | |
var valCount = (_formatter?.ValueNames?.Count ?? 0); | |
if (index < valCount) | |
{ | |
return _formatter.GetValue(_values, index); | |
} | |
else if (_additionalProperties is IReadOnlyList<KeyValuePair<string, object>> propList) | |
{ | |
return propList[index - valCount]; | |
} | |
else | |
{ | |
return _additionalProperties.Skip(index - valCount).First(); | |
} | |
} | |
} | |
} | |
public int Count | |
{ | |
get | |
{ | |
if (_formatter == null) | |
{ | |
return (_additionalProperties?.Count ?? 0) + 1; | |
} | |
return _formatter.ValueNames.Count + (_additionalProperties?.Count ?? 0) + 1; | |
} | |
} | |
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() | |
{ | |
if (_additionalProperties == null || _additionalProperties is IReadOnlyList<KeyValuePair<string, object>>) | |
{ | |
for (int i = 0; i < Count; ++i) | |
{ | |
yield return this[i]; | |
} | |
} | |
else | |
{ | |
var valCount = (_formatter?.ValueNames?.Count ?? 0); | |
for (int i = 0; i < valCount; ++i) | |
{ | |
yield return this[i]; | |
} | |
foreach (var prop in _additionalProperties) | |
{ | |
yield return prop; | |
} | |
yield return this[Count - 1]; | |
} | |
} | |
public override string ToString() | |
{ | |
if (_formatter == null) | |
{ | |
return _originalMessage; | |
} | |
return _formatter.Format(_values); | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
} | |
class CustomLogValuesFormatter | |
{ | |
private const string NullValue = "(null)"; | |
private static readonly object[] EmptyArray = new object[0]; | |
private static readonly char[] FormatDelimiters = { ',', ':' }; | |
private readonly string _format; | |
private readonly List<string> _valueNames = new List<string>(); | |
public CustomLogValuesFormatter(string format) | |
{ | |
OriginalFormat = format; | |
var sb = new StringBuilder(); | |
var scanIndex = 0; | |
var endIndex = format.Length; | |
while (scanIndex < endIndex) | |
{ | |
var openBraceIndex = FindBraceIndex(format, '{', scanIndex, endIndex); | |
var closeBraceIndex = FindBraceIndex(format, '}', openBraceIndex, endIndex); | |
if (closeBraceIndex == endIndex) | |
{ | |
sb.Append(format, scanIndex, endIndex - scanIndex); | |
scanIndex = endIndex; | |
} | |
else | |
{ | |
// Format item syntax : { index[,alignment][ :formatString] }. | |
var formatDelimiterIndex = FindIndexOfAny(format, FormatDelimiters, openBraceIndex, closeBraceIndex); | |
sb.Append(format, scanIndex, openBraceIndex - scanIndex + 1); | |
sb.Append(_valueNames.Count.ToString(CultureInfo.InvariantCulture)); | |
_valueNames.Add(format.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1)); | |
sb.Append(format, formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1); | |
scanIndex = closeBraceIndex + 1; | |
} | |
} | |
_format = sb.ToString(); | |
} | |
public string OriginalFormat { get; private set; } | |
public List<string> ValueNames => _valueNames; | |
private static int FindBraceIndex(string format, char brace, int startIndex, int endIndex) | |
{ | |
// Example: {{prefix{{{Argument}}}suffix}}. | |
var braceIndex = endIndex; | |
var scanIndex = startIndex; | |
var braceOccurenceCount = 0; | |
while (scanIndex < endIndex) | |
{ | |
if (braceOccurenceCount > 0 && format[scanIndex] != brace) | |
{ | |
if (braceOccurenceCount % 2 == 0) | |
{ | |
// Even number of '{' or '}' found. Proceed search with next occurence of '{' or '}'. | |
braceOccurenceCount = 0; | |
braceIndex = endIndex; | |
} | |
else | |
{ | |
// An unescaped '{' or '}' found. | |
break; | |
} | |
} | |
else if (format[scanIndex] == brace) | |
{ | |
if (brace == '}') | |
{ | |
if (braceOccurenceCount == 0) | |
{ | |
// For '}' pick the first occurence. | |
braceIndex = scanIndex; | |
} | |
} | |
else | |
{ | |
// For '{' pick the last occurence. | |
braceIndex = scanIndex; | |
} | |
braceOccurenceCount++; | |
} | |
scanIndex++; | |
} | |
return braceIndex; | |
} | |
private static int FindIndexOfAny(string format, char[] chars, int startIndex, int endIndex) | |
{ | |
var findIndex = format.IndexOfAny(chars, startIndex, endIndex - startIndex); | |
return findIndex == -1 ? endIndex : findIndex; | |
} | |
public string Format(object[] values) | |
{ | |
if (values != null) | |
{ | |
for (int i = 0; i < values.Length; i++) | |
{ | |
values[i] = FormatArgument(values[i]); | |
} | |
} | |
return string.Format(CultureInfo.InvariantCulture, _format, values ?? EmptyArray); | |
} | |
internal string Format() | |
{ | |
return _format; | |
} | |
internal string Format(object arg0) | |
{ | |
return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0)); | |
} | |
internal string Format(object arg0, object arg1) | |
{ | |
return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0), FormatArgument(arg1)); | |
} | |
internal string Format(object arg0, object arg1, object arg2) | |
{ | |
return string.Format(CultureInfo.InvariantCulture, _format, FormatArgument(arg0), FormatArgument(arg1), FormatArgument(arg2)); | |
} | |
public KeyValuePair<string, object> GetValue(object[] values, int index) | |
{ | |
if (index < 0 || index > _valueNames.Count) | |
{ | |
throw new IndexOutOfRangeException(nameof(index)); | |
} | |
if (_valueNames.Count > index) | |
{ | |
return new KeyValuePair<string, object>(_valueNames[index], values[index]); | |
} | |
return new KeyValuePair<string, object>("{OriginalFormat}", OriginalFormat); | |
} | |
public IEnumerable<KeyValuePair<string, object>> GetValues(object[] values) | |
{ | |
var valueArray = new KeyValuePair<string, object>[values.Length + 1]; | |
for (var index = 0; index != _valueNames.Count; ++index) | |
{ | |
valueArray[index] = new KeyValuePair<string, object>(_valueNames[index], values[index]); | |
} | |
valueArray[valueArray.Length - 1] = new KeyValuePair<string, object>("{OriginalFormat}", OriginalFormat); | |
return valueArray; | |
} | |
private object FormatArgument(object value) | |
{ | |
if (value == null) | |
{ | |
return NullValue; | |
} | |
// since 'string' implements IEnumerable, special case it | |
if (value is string) | |
{ | |
return value; | |
} | |
// if the value implements IEnumerable, build a comma separated string. | |
var enumerable = value as IEnumerable; | |
if (enumerable != null) | |
{ | |
return string.Join(", ", enumerable.Cast<object>().Select(o => o ?? NullValue)); | |
} | |
return value; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Prototype for https://github.com/dotnet/extensions/issues/668