在哪里可以找到使用新的结构化日志格式格式化日志字符串及其参数的函数。我正在寻找“string.Format(...”的等效项,但用于结构化日志记录。
我在文档中没有找到信息:如何使用结构化日志记录
如果我定义自己的记录器,我会收到正确的格式化程序函数(我正在寻找的函数),所以它应该存在于某处???在哪里以及如何使用它?
例如:
string relatedBug = "toto";
return FuncICannotFindToFormatLog("Log a bug related to {relatedBug}", relatedBug);
result ==> "Log a bug related to toto"
我想我找到了它,它似乎是 Microsoft 内部的。这是代码(稍微调整以与我的代码一起运行)。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace General.LogEx
{
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/// <summary>
/// Formatter to convert the named format items like {NamedformatItem} to <see cref="string.Format(IFormatProvider, string, object)"/> format.
/// </summary>
internal sealed class LogValuesFormatter
{
private const string NullValue = "(null)";
private static readonly char[] FormatDelimiters = { ',', ':' };
private readonly string _format;
private readonly List<string> _valueNames = new List<string>();
// NOTE: If this assembly ever builds for netcoreapp, the below code should change to:
// - Be annotated as [SkipLocalsInit] to avoid zero'ing the stackalloc'd char span
// - Format _valueNames.Count directly into a span
public LogValuesFormatter(string format)
{
ArgumentNullException.ThrowIfNull(format);
OriginalFormat = format;
var vsb = new ValueStringBuilder(stackalloc char[256]);
int scanIndex = 0;
int endIndex = format.Length;
while (scanIndex < endIndex)
{
int openBraceIndex = FindBraceIndex(format, '{', scanIndex, endIndex);
if (scanIndex == 0 && openBraceIndex == endIndex)
{
// No holes found.
_format = format;
return;
}
int closeBraceIndex = FindBraceIndex(format, '}', openBraceIndex, endIndex);
if (closeBraceIndex == endIndex)
{
vsb.Append(format.AsSpan(scanIndex, endIndex - scanIndex));
scanIndex = endIndex;
}
else
{
// Format item syntax : { index[,alignment][ :formatString] }.
int formatDelimiterIndex = FindIndexOfAny(format, FormatDelimiters, openBraceIndex, closeBraceIndex);
vsb.Append(format.AsSpan(scanIndex, openBraceIndex - scanIndex + 1));
vsb.Append(_valueNames.Count.ToString());
_valueNames.Add(format.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1));
vsb.Append(format.AsSpan(formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1));
scanIndex = closeBraceIndex + 1;
}
}
_format = vsb.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}}.
int braceIndex = endIndex;
int scanIndex = startIndex;
int braceOccurrenceCount = 0;
while (scanIndex < endIndex)
{
if (braceOccurrenceCount > 0 && format[scanIndex] != brace)
{
if (braceOccurrenceCount % 2 == 0)
{
// Even number of '{' or '}' found. Proceed search with next occurrence of '{' or '}'.
braceOccurrenceCount = 0;
braceIndex = endIndex;
}
else
{
// An unescaped '{' or '}' found.
break;
}
}
else if (format[scanIndex] == brace)
{
if (brace == '}')
{
if (braceOccurrenceCount == 0)
{
// For '}' pick the first occurrence.
braceIndex = scanIndex;
}
}
else
{
// For '{' pick the last occurrence.
braceIndex = scanIndex;
}
braceOccurrenceCount++;
}
scanIndex++;
}
return braceIndex;
}
private static int FindIndexOfAny(string format, char[] chars, int startIndex, int endIndex)
{
int findIndex = format.IndexOfAny(chars, startIndex, endIndex - startIndex);
return findIndex == -1 ? endIndex : findIndex;
}
public string Format(object?[]? values)
{
object?[]? formattedValues = values;
if (values != null)
{
for (int i = 0; i < values.Length; i++)
{
object formattedValue = FormatArgument(values[i]);
// If the formatted value is changed, we allocate and copy items to a new array to avoid mutating the array passed in to this method
if (!ReferenceEquals(formattedValue, values[i]))
{
formattedValues = new object[values.Length];
Array.Copy(values, formattedValues, i);
formattedValues[i++] = formattedValue;
for (; i < values.Length; i++)
{
formattedValues[i] = FormatArgument(values[i]);
}
break;
}
}
}
return string.Format(CultureInfo.InvariantCulture, _format, formattedValues ?? Array.Empty<object>());
}
// NOTE: This method mutates the items in the array if needed to avoid extra allocations, and should only be used when caller expects this to happen
internal string FormatWithOverwrite(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 ?? Array.Empty<object>());
}
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 (int 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.
if (value is IEnumerable enumerable)
{
var vsb = new ValueStringBuilder(stackalloc char[256]);
bool first = true;
foreach (object? e in enumerable)
{
if (!first)
{
vsb.Append(", ");
}
vsb.Append(e != null ? e.ToString() : NullValue);
first = false;
}
return vsb.ToString();
}
return value;
}
}
}
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Text;
internal ref struct ValueStringBuilder
{
private char[] _arrayToReturnToPool;
private Span<char> _chars;
private int _pos;
public int Length
{
get
{
return _pos;
}
set
{
_pos = value;
}
}
public int Capacity => _chars.Length;
public ref char this[int index] => ref _chars[index];
public Span<char> RawChars => _chars;
public ValueStringBuilder(Span<char> initialBuffer)
{
_arrayToReturnToPool = null;
_chars = initialBuffer;
_pos = 0;
}
public ValueStringBuilder(int initialCapacity)
{
_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
_chars = _arrayToReturnToPool;
_pos = 0;
}
public void EnsureCapacity(int capacity)
{
if ((uint)capacity > (uint)_chars.Length)
{
Grow(capacity - _pos);
}
}
public ref char GetPinnableReference()
{
return ref MemoryMarshal.GetReference(_chars);
}
public ref char GetPinnableReference(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return ref MemoryMarshal.GetReference(_chars);
}
public override string ToString()
{
string result = _chars.Slice(0, _pos).ToString();
Dispose();
return result;
}
public ReadOnlySpan<char> AsSpan(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return _chars.Slice(0, _pos);
}
public ReadOnlySpan<char> AsSpan()
{
return _chars.Slice(0, _pos);
}
public ReadOnlySpan<char> AsSpan(int start)
{
return _chars.Slice(start, _pos - start);
}
public ReadOnlySpan<char> AsSpan(int start, int length)
{
return _chars.Slice(start, length);
}
public bool TryCopyTo(Span<char> destination, out int charsWritten)
{
if (_chars.Slice(0, _pos).TryCopyTo(destination))
{
charsWritten = _pos;
Dispose();
return true;
}
charsWritten = 0;
Dispose();
return false;
}
public void Insert(int index, char value, int count)
{
if (_pos > _chars.Length - count)
{
Grow(count);
}
int length = _pos - index;
_chars.Slice(index, length).CopyTo(_chars.Slice(index + count));
_chars.Slice(index, count).Fill(value);
_pos += count;
}
public void Insert(int index, string? s)
{
if (s != null)
{
int length = s.Length;
if (_pos > _chars.Length - length)
{
Grow(length);
}
int length2 = _pos - index;
_chars.Slice(index, length2).CopyTo(_chars.Slice(index + length));
s.CopyTo(_chars.Slice(index));
_pos += length;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(char c)
{
int pos = _pos;
if ((uint)pos < (uint)_chars.Length)
{
_chars[pos] = c;
_pos = pos + 1;
}
else
{
GrowAndAppend(c);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string? s)
{
if (s != null)
{
int pos = _pos;
if (s.Length == 1 && (uint)pos < (uint)_chars.Length)
{
_chars[pos] = s[0];
_pos = pos + 1;
}
else
{
AppendSlow(s);
}
}
}
private void AppendSlow(string s)
{
int pos = _pos;
if (pos > _chars.Length - s.Length)
{
Grow(s.Length);
}
s.CopyTo(_chars.Slice(pos));
_pos += s.Length;
}
public void Append(char c, int count)
{
if (_pos > _chars.Length - count)
{
Grow(count);
}
Span<char> span = _chars.Slice(_pos, count);
for (int i = 0; i < span.Length; i++)
{
span[i] = c;
}
_pos += count;
}
//public unsafe void Append(char* value, int length)
//{
// int pos = _pos;
// if (pos > _chars.Length - length)
// {
// Grow(length);
// }
// Span<char> span = _chars.Slice(_pos, length);
// for (int i = 0; i < span.Length; i++)
// {
// span[i] = *(value++);
// }
// _pos += length;
//}
public void Append(ReadOnlySpan<char> value)
{
int pos = _pos;
if (pos > _chars.Length - value.Length)
{
Grow(value.Length);
}
value.CopyTo(_chars.Slice(_pos));
_pos += value.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<char> AppendSpan(int length)
{
int pos = _pos;
if (pos > _chars.Length - length)
{
Grow(length);
}
_pos = pos + length;
return _chars.Slice(pos, length);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowAndAppend(char c)
{
Grow(1);
Append(c);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacityBeyondPos)
{
int minimumLength = (int)Math.Max((uint)(_pos + additionalCapacityBeyondPos), Math.Min((uint)(_chars.Length * 2), 2147483591u));
char[] array = ArrayPool<char>.Shared.Rent(minimumLength);
_chars.Slice(0, _pos).CopyTo(array);
char[] arrayToReturnToPool = _arrayToReturnToPool;
_chars = (_arrayToReturnToPool = array);
if (arrayToReturnToPool != null)
{
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
char[] arrayToReturnToPool = _arrayToReturnToPool;
this = default(ValueStringBuilder);
if (arrayToReturnToPool != null)
{
ArrayPool<char>.Shared.Return(arrayToReturnToPool);
}
}
}