Skip to content

Commit

Permalink
improve ZLoggerInterpolatedStringHandler(use MagicalBox and etc)
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Oct 18, 2023
1 parent c368cae commit 94199e9
Show file tree
Hide file tree
Showing 8 changed files with 479 additions and 77 deletions.
2 changes: 1 addition & 1 deletion src/ZLogger.MessagePack/MessagePackZLoggerFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ public void FormatLogEntry<TEntry>(IBufferWriter<byte> writer, TEntry entry) whe
var value = entry.GetParameterValue<DateTimeOffset?>(i);
MessagePackSerializer.Serialize(valueType, ref messagePackWriter, value, MessagePackSerializerOptions);
}
else
else // TODO: GUID?
{
var boxedValue = entry.GetParameterValue(i);
MessagePackSerializer.Serialize(valueType, ref messagePackWriter, boxedValue, MessagePackSerializerOptions);
Expand Down
130 changes: 130 additions & 0 deletions src/ZLogger/Internal/MagicalBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Json;
using Utf8StringInterpolation;

namespace ZLogger.Internal;

internal unsafe struct MagicalBox
{
byte[] storage;
int written;

public MagicalBox(byte[] storage)
{
this.storage = storage;
}

public int Written => written;

public ReadOnlySpan<byte> AsSpan() => storage.AsSpan(0, written);

public bool TryWrite<T>(T value, out int offset)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
offset = 0;
return false;
}

var size = Unsafe.SizeOf<T>();
if (storage.Length < written + size)
{
offset = 0;
return false;
}

Unsafe.WriteUnaligned(ref storage[written], value);
offset = written;
written += Unsafe.SizeOf<T>();
return true;
}

public bool TryRead<T>(int offset, out T value)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()
|| offset < 0
|| (storage.Length < offset + Unsafe.SizeOf<T>()))
{
value = default!;
return false;
}

value = Unsafe.ReadUnaligned<T>(ref storage[offset]);
return true;
}

public T Read<T>(int offset)
{
if (!TryRead<T>(offset, out var value))
{
ThrowArgumentOutOfRangeException();
}
return value;
}

public object? Read(Type type, int offset)
{
// TODO: return boxed.
throw new NotImplementedException();
}

public bool TryReadTo(Type type, int offset, int alignment, string? format, ref Utf8StringWriter<IBufferWriter<byte>> handler)
{
if (offset < 0) return false;
// if (storage.Length < offset + Unsafe.SizeOf<T>())


//if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
//{
// return false;
//}

// TODO: many types...?
if (type == typeof(int))
{
handler.AppendFormatted(Read<int>(offset), alignment, format);
}

return true;
}

public bool TryReadTo(Type type, int offset, int alignment, string? format, ref DefaultInterpolatedStringHandler handler)
{
if (offset < 0) return false;

//if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
//{
// return false;
//}

if (type == typeof(int))
{
handler.AppendFormatted(Read<int>(offset), alignment, format);
}
return true;
}

public bool TryReadTo(Type type, int offset, string propertyName, Utf8JsonWriter jsonWriter)
{
if (offset < 0) return false;




if (type == typeof(int))
{
//jsonWriter.WriteNumberValue(

//handler.AppendFormatted(Read<int>(offset), alignment, format);
}
return true;
}

static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException();
}
}
106 changes: 62 additions & 44 deletions src/ZLogger/LogStates/InterpolatedStringLogState.cs
Original file line number Diff line number Diff line change
@@ -1,81 +1,87 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using ZLogger.Internal;

namespace ZLogger.LogStates
{
public readonly struct InterpolatedStringLogState : IZLoggerFormattable, IDisposable
internal struct InterpolatedStringLogState : IZLoggerFormattable, IDisposable
{
public IZLoggerEntry CreateEntry(LogInfo info)
{
// If state has cached value, require clone.
var newParameters = ArrayPool<KeyValuePair<string, object?>>.Shared.Rent(this.ParameterCount);
for (var i = 0; i < this.ParameterCount; i++)
{
newParameters[i] = this.parameters[i];
}
public int ParameterCount { get; }

var newBuffer = ArrayBufferWriterPool.Rent();
this.buffer.WrittenSpan.CopyTo(newBuffer.GetSpan(this.buffer.WrittenCount));
newBuffer.Advance(this.buffer.WrittenCount);
public bool IsSupportUtf8ParameterKey => false;

var newState = new InterpolatedStringLogState(newParameters, this.ParameterCount, newBuffer);
// pooling values.
byte[] magicalBoxStorage;
InterpolatedStringParameter[] parameters;

// Create Entry with cloned state
return ZLoggerEntry<InterpolatedStringLogState>.Create(info, newState);
}
readonly MessageSequence messageSequence;
readonly MagicalBox magicalBox;

public int ParameterCount { get; }
public InterpolatedStringLogState(MessageSequence messageSequence, MagicalBox magicalBox, ReadOnlySpan<InterpolatedStringParameter> parameters)
{
// need clone.
this.magicalBoxStorage = ArrayPool<byte>.Shared.Rent(magicalBox.Written);
magicalBox.AsSpan().CopyTo(magicalBoxStorage);

public bool IsSupportUtf8ParameterKey => false;
this.parameters = ArrayPool<InterpolatedStringParameter>.Shared.Rent(parameters.Length);
parameters.CopyTo(this.parameters);

readonly KeyValuePair<string, object?>[] parameters; // pooled
readonly ArrayBufferWriter<byte> buffer; // pooled
this.messageSequence = messageSequence;
this.magicalBox = new MagicalBox(magicalBoxStorage);
}

public InterpolatedStringLogState(KeyValuePair<string, object?>[] parameters, int parameterCount, ArrayBufferWriter<byte> buffer)
public IZLoggerEntry CreateEntry(LogInfo info)
{
this.parameters = parameters;
this.buffer = buffer;
ParameterCount = parameterCount;
// state needs clone.
var newState = new InterpolatedStringLogState(messageSequence, this.magicalBox, this.parameters);

// Create Entry with cloned state(state will dispose when entry was disposed)
return ZLoggerEntry<InterpolatedStringLogState>.Create(info, newState);
}

public void Dispose()
{
ArrayPool<KeyValuePair<string, object?>>.Shared.Return(parameters);
ArrayBufferWriterPool.Return(buffer);
if (magicalBoxStorage != null)
{
ArrayPool<byte>.Shared.Return(magicalBoxStorage);
ArrayPool<InterpolatedStringParameter>.Shared.Return(parameters);

magicalBoxStorage = null!;
parameters = null!;
}
}

public override string ToString()
{
return Encoding.UTF8.GetString(buffer.WrittenSpan);
return messageSequence.ToString(magicalBox, parameters);
}

public void ToString(IBufferWriter<byte> writer)
{
var written = buffer.WrittenSpan;
var dest = writer.GetSpan(written.Length);
written.CopyTo(dest);
writer.Advance(written.Length);
messageSequence.ToString(writer, magicalBox, parameters);
}

public void WriteJsonParameterKeyValues(Utf8JsonWriter jsonWriter, JsonSerializerOptions jsonSerializerOptions)
{
for (var i = 0; i < ParameterCount; i++)
{
var (key, value) = parameters[i];
jsonWriter.WritePropertyName(key);
if (value == null)
ref var p = ref parameters[i];
if (magicalBox.TryReadTo(p.Type, p.BoxOffset, p.Name, jsonWriter))
{
jsonWriter.WriteNullValue();
continue;
}

jsonWriter.WritePropertyName(p.Name);

var value = magicalBox.Read(p.Type, p.BoxOffset);
if (value != null)
{
JsonSerializer.Serialize(jsonWriter, value, p.Type, jsonSerializerOptions);
}
else
{
var valueType = GetParameterType(i);
JsonSerializer.Serialize(jsonWriter, value, valueType, jsonSerializerOptions); // TODO: more optimize ?
JsonSerializer.Serialize(jsonWriter, p.BoxedValue, p.Type, jsonSerializerOptions);
}
}
}
Expand All @@ -87,22 +93,34 @@ public ReadOnlySpan<byte> GetParameterKey(int index)

public string GetParameterKeyAsString(int index)
{
return parameters[index].Key;
return parameters[index].Name;
}

public object? GetParameterValue(int index)
{
return parameters[index].Value;
ref var p = ref parameters[index];
var value = magicalBox.Read(p.Type, p.BoxOffset);
if (value != null) return value;

return p.BoxedValue;
}

public T? GetParameterValue<T>(int index)
{
return (T?)parameters[index].Value;
ref var p = ref parameters[index];
if (magicalBox.TryRead<T>(p.BoxOffset, out var value))
{
return value;
}
else
{
return (T?)p.BoxedValue;
}
}

public Type GetParameterType(int index)
{
return parameters[index].Value?.GetType() ?? typeof(string);
return parameters[index].Type;
}
}
}
2 changes: 0 additions & 2 deletions src/ZLogger/LogStates/StringFormatterLogState.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;

Expand Down
22 changes: 12 additions & 10 deletions src/ZLogger/ZLogger.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
<PropertyGroup>
<TargetFrameworks>net6.0;</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>9.0</LangVersion>
<LangVersion>11.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1701;1702;1591;1573</NoWarn>

<!-- NuGet Packaging -->
<PackageTags>logging;</PackageTags>
<Description>Zero Allocation Text/Strcutured Logger for .NET Core, built on top of a Microsoft.Extensions.Logging.</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -42,19 +44,19 @@
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
</Compile>
<Compile Update="ZLoggerExtensions.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
</Compile>
<Compile Update="ZLoggerExtensions.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
</Compile>
<Compile Update="ZLoggerExtensions.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ZLoggerExtensions.tt</DependentUpon>
</Compile>
</ItemGroup>

Expand Down
5 changes: 5 additions & 0 deletions src/ZLogger/ZLoggerEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public void FormatUtf8(IBufferWriter<byte> writer, IZLoggerFormatter formatter)

public void Return()
{
if (state is IDisposable)
{
((IDisposable)state).Dispose();
}

state = default!;
logInfo = default!;
ScopeState?.Return();
Expand Down
2 changes: 1 addition & 1 deletion src/ZLogger/ZLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void ZLog(this ILogger logger, LogLevel logLevel, Exception? excep

public static void ZLog(this ILogger logger, LogLevel logLevel, EventId eventId, Exception? exception, ref ZLoggerInterpolatedStringHandler message)
{
using var state = message.GetState();
using var state = message.GetStateAndClear();
logger.Log(logLevel, eventId, state, exception, (state, ex) => state.ToString());
}

Expand Down
Loading

0 comments on commit 94199e9

Please sign in to comment.