Appenders
PrefixWriter.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.Formatting;
using System.Text.RegularExpressions;
using System.Threading;
namespace ZeroLog.Appenders
{
internal clast PrefixWriter
{
private const int _maxLength = 512;
private const string _dateFormat = "yyyy-MM-dd";
private readonly StringBuffer _stringBuffer = new StringBuffer(_maxLength);
private readonly byte[] _buffer = new byte[_maxLength * sizeof(char)];
private readonly char[] _strings;
private readonly Action _appendMethod;
public PrefixWriter(string pattern)
{
var parts = OptimizeParts(ParsePattern(pattern)).ToList();
_strings = BuildStrings(parts, out var stringMap);
_appendMethod = BuildAppendMethod(parts, stringMap);
}
private static IEnumerable ParsePattern(string pattern)
{
var position = 0;
foreach (Match? match in Regex.Matches(pattern, @"%(?:(?\w+)|\{\s*(?\w+)\s*\})", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
{
if (position < match!.Index)
yield return new PatternPart(pattern.Substring(position, match.Index - position));
yield return match.Groups["part"].Value.ToLowerInvariant() switch
{
"date" => new PatternPart(PatternPartType.Date),
"time" => new PatternPart(PatternPartType.Time),
"thread" => new PatternPart(PatternPartType.Thread),
"level" => new PatternPart(PatternPartType.Level),
"logger" => new PatternPart(PatternPartType.Logger),
_ => new PatternPart(match.Value)
};
position = match.Index + match.Length;
}
if (position < pattern.Length)
yield return new PatternPart(pattern.Substring(position, pattern.Length - position));
}
private static IEnumerable OptimizeParts(IEnumerable parts)
{
var currentString = string.Empty;
foreach (var part in parts)
{
if (part.Type == PatternPartType.String)
{
currentString += part.Value;
}
else
{
if (currentString.Length != 0)
{
yield return new PatternPart(currentString);
currentString = string.Empty;
}
yield return part;
}
}
if (currentString.Length != 0)
yield return new PatternPart(currentString);
}
private static char[] BuildStrings(IEnumerable parts, out Dictionary map)
{
var stringOffsets = new Dictionary();
var stringsBuilder = new StringBuilder();
foreach (var part in parts)
{
switch (part.Type)
{
case PatternPartType.String:
AddString(part.Value!);
break;
case PatternPartType.Date:
AddString(_dateFormat);
break;
}
}
void AddString(string value)
{
if (stringOffsets.ContainsKey(value))
return;
var offset = stringsBuilder.Length;
stringsBuilder.Append(value);
stringOffsets[value] = (offset, value.Length);
}
if (stringsBuilder.Length == 0)
AddString(" ");
var strings = new char[stringsBuilder.Length];
stringsBuilder.CopyTo(0, strings, 0, stringsBuilder.Length);
map = stringOffsets;
return strings;
}
private static Action BuildAppendMethod(ICollection parts, Dictionary stringMap)
{
var method = new DynamicMethod("WritePrefix", typeof(void), new[] { typeof(PrefixWriter), typeof(ILogEventHeader) }, typeof(PrefixWriter), false)
{
InitLocals = false
};
var il = method.GetILGenerator();
var stringBufferLocal = il.DeclareLocal(typeof(StringBuffer));
var stringsLocal = il.DeclareLocal(typeof(char).MakeByRefType(), true);
var dateTimeLocal = default(LocalBuilder);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeof(PrefixWriter).GetField(nameof(_stringBuffer), BindingFlags.Instance | BindingFlags.NonPublic)!);
il.Emit(OpCodes.Stloc, stringBufferLocal);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeof(PrefixWriter).GetField(nameof(_strings), BindingFlags.Instance | BindingFlags.NonPublic)!);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, typeof(char));
il.Emit(OpCodes.Stloc, stringsLocal);
foreach (var part in parts)
{
switch (part.Type)
{
case PatternPartType.String:
{
// _stringBuffer.Append(&_strings[0] + offset * sizeof(char), length);
var (offset, length) = stringMap[part.Value!];
il.Emit(OpCodes.Ldloc, stringBufferLocal);
il.Emit(OpCodes.Ldloc, stringsLocal);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Ldc_I4, offset * sizeof(char));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldc_I4, length);
il.Emit(OpCodes.Call, typeof(StringBuffer).GetMethod(nameof(StringBuffer.Append), new[] { typeof(char*), typeof(int) })!);
break;
}
case PatternPartType.Date:
{
// _stringBuffer.Append(logEventHeader.Timestamp, new StringView(&_strings[0] + offset * sizeof(char), length));
var (offset, length) = stringMap[_dateFormat];
il.Emit(OpCodes.Ldloc, stringBufferLocal);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, typeof(ILogEventHeader).GetProperty(nameof(ILogEventHeader.Timestamp))?.GetGetMethod()!);
il.Emit(OpCodes.Ldloc, stringsLocal);
il.Emit(OpCodes.Conv_U);
il.Emit(OpCodes.Ldc_I4, offset * sizeof(char));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldc_I4, length);
il.Emit(OpCodes.Newobj, typeof(StringView).GetConstructor(new[] { typeof(char*), typeof(int) })!);
il.Emit(OpCodes.Call, typeof(StringBuffer).GetMethod(nameof(StringBuffer.Append), new[] { typeof(DateTime), typeof(StringView) })!);
break;
}
case PatternPartType.Time:
{
// _stringBuffer.Append(logEventHeader.Timestamp.TimeOfDay, StringView.Empty);
il.Emit(OpCodes.Ldloc, stringBufferLocal);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, typeof(ILogEventHeader).GetProperty(nameof(ILogEventHeader.Timestamp))?.GetGetMethod()!);
il.Emit(OpCodes.Stloc, dateTimeLocal ??= il.DeclareLocal(typeof(DateTime)));
il.Emit(OpCodes.Ldloca, dateTimeLocal);
il.Emit(OpCodes.Call, typeof(DateTime).GetProperty(nameof(DateTime.TimeOfDay))?.GetGetMethod()!);
il.Emit(OpCodes.Ldsfld, typeof(StringView).GetField(nameof(StringView.Empty))!);
il.Emit(OpCodes.Call, typeof(StringBuffer).GetMethod(nameof(StringBuffer.Append), new[] { typeof(TimeSpan), typeof(StringView) })!);
break;
}
case PatternPartType.Thread:
{
// AppendThread(logEventHeader.Thread);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, typeof(ILogEventHeader).GetProperty(nameof(ILogEventHeader.Thread))?.GetGetMethod()!);
il.Emit(OpCodes.Call, typeof(PrefixWriter).GetMethod(nameof(AppendThread), BindingFlags.Instance | BindingFlags.NonPublic)!);
break;
}
case PatternPartType.Level:
{
// _stringBuffer.Append(LevelStringCache.GetLevelString(logEventHeader.Level));
il.Emit(OpCodes.Ldloc, stringBufferLocal);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, typeof(ILogEventHeader).GetProperty(nameof(ILogEventHeader.Level))?.GetGetMethod()!);
il.Emit(OpCodes.Call, typeof(LevelStringCache).GetMethod(nameof(LevelStringCache.GetLevelString))!);
il.Emit(OpCodes.Call, typeof(StringBuffer).GetMethod(nameof(StringBuffer.Append), new[] { typeof(string) })!);
break;
}
case PatternPartType.Logger:
{
// _stringBuffer.Append(logEventHeader.Name);
il.Emit(OpCodes.Ldloc, stringBufferLocal);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Callvirt, typeof(ILogEventHeader).GetProperty(nameof(ILogEventHeader.Name))?.GetGetMethod()!);
il.Emit(OpCodes.Call, typeof(StringBuffer).GetMethod(nameof(StringBuffer.Append), new[] { typeof(string) })!);
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
il.Emit(OpCodes.Ret);
return (Action)method.CreateDelegate(typeof(Action));
}
public unsafe int WritePrefix(Stream stream, ILogEventHeader logEventHeader, Encoding encoding)
{
_stringBuffer.Clear();
_appendMethod(this, logEventHeader);
int bytesWritten;
fixed (byte* buf = &_buffer[0])
bytesWritten = _stringBuffer.CopyTo(buf, _buffer.Length, 0, _stringBuffer.Count, encoding);
stream.Write(_buffer, 0, bytesWritten);
return bytesWritten;
}
internal void AppendThread(Thread? thread)
{
if (thread != null)
{
if (thread.Name != null)
_stringBuffer.Append(thread.Name);
else
_stringBuffer.Append(thread.ManagedThreadId, StringView.Empty);
}
else
{
_stringBuffer.Append('0');
}
}
private enum PatternPartType
{
String,
Date,
Time,
Thread,
Level,
Logger
}
private readonly struct PatternPart
{
public PatternPartType Type { get; }
public string? Value { get; }
public PatternPart(PatternPartType type)
{
Type = type;
Value = null;
}
public PatternPart(string value)
{
Type = PatternPartType.String;
Value = value;
}
}
}
}