Skip to content

Instantly share code, notes, and snippets.

@TheXenocide
Created January 8, 2020 21:58
Show Gist options
  • Save TheXenocide/f7d8a249ec19857dee926426e2319746 to your computer and use it in GitHub Desktop.
Save TheXenocide/f7d8a249ec19857dee926426e2319746 to your computer and use it in GitHub Desktop.
StructuredLoggerExtensions
/// <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;
}
}
}
@TheXenocide
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment