diff --git a/src/ZeroLog.Tests/Extensions.cs b/src/ZeroLog.Tests/Extensions.cs new file mode 100644 index 00000000..f982d295 --- /dev/null +++ b/src/ZeroLog.Tests/Extensions.cs @@ -0,0 +1,10 @@ +using System.Text.Formatting; + +namespace ZeroLog.Tests +{ + internal static class Extensions + { + public static void WriteToStringBuffer(this IInternalLogEvent logEvent, StringBuffer stringBuffer) + => logEvent.WriteToStringBuffer(stringBuffer, new KeyValuePointerBuffer()); + } +} diff --git a/src/ZeroLog.Tests/LogEventTests.EdgeCases.cs b/src/ZeroLog.Tests/LogEventTests.EdgeCases.cs index 5806071b..a17162ec 100644 --- a/src/ZeroLog.Tests/LogEventTests.EdgeCases.cs +++ b/src/ZeroLog.Tests/LogEventTests.EdgeCases.cs @@ -40,7 +40,9 @@ public void should_truncate_ascii_string_if_buffer_is_not_large_enough() } [Test] - public void should_ignore_ascii_string_if_buffer_is_not_large_enough_for_header([Range(_bufferSize - 2 * _asciiHeaderSize, _bufferSize)] int firstStringLength) + public void should_ignore_ascii_string_if_buffer_is_not_large_enough_for_header( + [Range(_bufferSize - 2 * _asciiHeaderSize, _bufferSize)] + int firstStringLength) { var largeString1 = new string('a', firstStringLength); var asciiBytes1 = Encoding.ASCII.GetBytes(largeString1); @@ -212,6 +214,18 @@ public void should_ignore_append_time_span_if_buffer_is_full() Check.That(string.IsNullOrWhiteSpace(_output.ToString())); } + [Test] + public void should_ignore_append_key_values_if_buffer_is_full() + { + FillBufferWithWhiteSpaces(); + _logEvent.AppendKeyValue("key1", (string)null) + .AppendKeyValue("key2", "val2") + .AppendKeyValue("key3", 3); + _logEvent.WriteToStringBuffer(_output); + + Check.That(string.IsNullOrWhiteSpace(_output.ToString())); + } + private void FillBufferWithWhiteSpaces() { var largeString = new string(' ', _bufferSize); diff --git a/src/ZeroLog.Tests/LogEventTests.Enum.cs b/src/ZeroLog.Tests/LogEventTests.Enum.cs index f0c189d3..8a291bfa 100644 --- a/src/ZeroLog.Tests/LogEventTests.Enum.cs +++ b/src/ZeroLog.Tests/LogEventTests.Enum.cs @@ -39,6 +39,50 @@ public void should_append_null_enum() Assert.AreEqual("null", _output.ToString()); } + [Test] + public void should_append_enum_key_value() + { + LogManager.RegisterEnum(typeof(TestEnum)); + + _logEvent.AppendKeyValue("myKey", TestEnum.Bar); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": \"Bar\" }", _output.ToString()); + } + + [Test] + public void should_append_nullable_enum_key_value() + { + LogManager.RegisterEnum(typeof(TestEnum)); + + _logEvent.AppendKeyValue("myKey", (TestEnum?)TestEnum.Bar); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": \"Bar\" }", _output.ToString()); + } + + [Test] + public void should_append_null_enum_key_value() + { + LogManager.RegisterEnum(typeof(TestEnum)); + + _logEvent.AppendKeyValue("myKey", (TestEnum?)null); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": null }", _output.ToString()); + } + + [Test] + public void should_append_unknown_enum_key_value() + { + LogManager.RegisterEnum(typeof(TestEnum)); + + _logEvent.AppendKeyValue("myKey", (TestEnum)(-42)); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": -42 }", _output.ToString()); + } + [Test] public void should_append_enum_generic() { diff --git a/src/ZeroLog.Tests/LogEventTests.KeyValues.cs b/src/ZeroLog.Tests/LogEventTests.KeyValues.cs new file mode 100644 index 00000000..d2843f7c --- /dev/null +++ b/src/ZeroLog.Tests/LogEventTests.KeyValues.cs @@ -0,0 +1,208 @@ +using System; +using NUnit.Framework; + +namespace ZeroLog.Tests +{ + public partial class LogEventTests + { + [Test] + public void should_append_single_key_value() + { + _logEvent.AppendKeyValue("myKey", "myValue"); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": \"myValue\" }", _output.ToString()); + } + + [Test] + public void should_append_multiple_key_values() + { + _logEvent.AppendKeyValue("myKey", "myValue"); + _logEvent.AppendKeyValue("otherKey", 2); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual(" ~~ { \"myKey\": \"myValue\", \"otherKey\": 2 }", _output.ToString()); + } + + [Test] + public void should_append_formatted_string_mixed_with_key_values() + { + // TODO(lmanners): There are more edge cases here. + _logEvent.AppendKeyValue("myKey", "myValue"); + _logEvent.AppendFormat("Some {} message"); + _logEvent.Append("formatted"); + _logEvent.AppendKeyValue("otherKey", 2); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual("Some formatted message ~~ { \"myKey\": \"myValue\", \"otherKey\": 2 }", _output.ToString()); + } + + [Test] + public void should_be_chainable() + { + _logEvent.AppendKeyValue("myKey", 1.1f).AppendKeyValue("otherKey", new Guid()); + _logEvent.Append("message"); + _logEvent.WriteToStringBuffer(_output); + + Assert.AreEqual("message ~~ { \"myKey\": 1.1, \"otherKey\": \"00000000-0000-0000-0000-000000000000\" }", _output.ToString()); + } + + [TestCase] + public void should_support_char() + { + _logEvent.AppendKeyValue("key1", 'a'); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key1\": \"a\" }", _output.ToString()); + } + + [TestCase] + public void should_support_datetime() + { + _logEvent.AppendKeyValue("key1", DateTime.MinValue); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key1\": \"0001-01-01 00:00:00.000\" }", _output.ToString()); + } + + [TestCase] + public void should_support_boolean() + { + _logEvent.AppendKeyValue("key1", true).AppendKeyValue("key2", false); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key1\": true, \"key2\": false }", _output.ToString()); + } + + [TestCase] + public void should_support_single_null_key_value() + { + _logEvent.AppendKeyValue("key1", (int?)null); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key1\": null }", _output.ToString()); + } + + [TestCase] + public void should_support_null_string_key_value() + { + _logEvent.AppendKeyValue("key1", "val1") + .AppendKeyValue("key2", (string)null) + .AppendKeyValue("key3", 3); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key1\": \"val1\", \"key2\": null, \"key3\": 3 }", _output.ToString()); + } + + [TestCase] + public void should_support_number_types() + { + _logEvent.AppendKeyValue("byte", (byte)1) + .AppendKeyValue("short", (short)2) + .AppendKeyValue("int", 3) + .AppendKeyValue("long", 4L) + .AppendKeyValue("float", 5.5f) + .AppendKeyValue("double", 6.6d) + .AppendKeyValue("decimal", 6.6m); + + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"byte\": 1, \"short\": 2, \"int\": 3, \"long\": 4, \"float\": 5.5, \"double\": 6.6, \"decimal\": 6.6 }", + _output.ToString()); + } + + [TestCase] + public void should_handle_truncated_key_values() + { + _logEvent.Initialize(Level.Info, null, LogEventArgumentExhaustionStrategy.TruncateMessage); + for (var i = 0; i < 6; i++) + { + _logEvent.AppendKeyValue($"key{i}", $"value{i}"); + } + + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual( + " ~~ { \"key0\": \"value0\", \"key1\": \"value1\", \"key2\": \"value2\", \"key3\": \"value3\", \"key4\": \"value4\" } [TRUNCATED]", + _output.ToString()); + } + + [TestCase] + public void should_allocate_space_for_more_key_values() + { + for (var i = 0; i < 6; i++) + { + _logEvent.AppendKeyValue($"key{i}", $"value{i}"); + } + + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual( + " ~~ { \"key0\": \"value0\", \"key1\": \"value1\", \"key2\": \"value2\", \"key3\": \"value3\", \"key4\": \"value4\", \"key5\": \"value5\" }", + _output.ToString()); + } + + [TestCase('\\', "\\\\")] + [TestCase('"', "\\\"")] + [TestCase('\u0000', "\\u0000")] + [TestCase('\u0000', "\\u0000")] + [TestCase('\u0001', "\\u0001")] + [TestCase('\u0002', "\\u0002")] + [TestCase('\u0003', "\\u0003")] + [TestCase('\u0004', "\\u0004")] + [TestCase('\u0005', "\\u0005")] + [TestCase('\u0006', "\\u0006")] + [TestCase('\u0007', "\\u0007")] + [TestCase('\u0008', "\\b")] + [TestCase('\u0009', "\\t")] + [TestCase('\u000A', "\\n")] + [TestCase('\u000B', "\\u000b")] + [TestCase('\u000C', "\\f")] + [TestCase('\u000D', "\\r")] + [TestCase('\u000E', "\\u000e")] + [TestCase('\u000F', "\\u000f")] + [TestCase('\u0010', "\\u0010")] + [TestCase('\u0011', "\\u0011")] + [TestCase('\u0012', "\\u0012")] + [TestCase('\u0013', "\\u0013")] + [TestCase('\u0014', "\\u0014")] + [TestCase('\u0015', "\\u0015")] + [TestCase('\u0016', "\\u0016")] + [TestCase('\u0017', "\\u0017")] + [TestCase('\u0018', "\\u0018")] + [TestCase('\u0019', "\\u0019")] + [TestCase('\u001A', "\\u001a")] + [TestCase('\u001B', "\\u001b")] + [TestCase('\u001C', "\\u001c")] + [TestCase('\u001D', "\\u001d")] + [TestCase('\u001E', "\\u001e")] + [TestCase('\u001F', "\\u001f")] + public void should_handle_escaped_characters(char character, string expected) + { + _logEvent.AppendKeyValue("myKey", character); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"myKey\": \"" + expected + "\" }", _output.ToString()); + } + + [TestCase] + public void should_handle_partially_truncated_key_value() + { + _logEvent.Initialize(Level.Info, null, LogEventArgumentExhaustionStrategy.TruncateMessage); + + // This value 'consumes' one slot in the buffer. This will cause us to be out of space when appending the value for key4. + _logEvent.Append("msg"); + + for (var i = 0; i < 5; i++) + { + _logEvent.AppendKeyValue($"key{i}", $"value{i}"); + } + + _logEvent.WriteToStringBuffer(_output); + + // 'key4' is not present because there wasn't space for its value. + Assert.AreEqual( + "msg ~~ { \"key0\": \"value0\", \"key1\": \"value1\", \"key2\": \"value2\", \"key3\": \"value3\" } [TRUNCATED]", + _output.ToString()); + } + + [TestCase] + public void should_escape_strings_for_json() + { + _logEvent.AppendKeyValue("key \\ \" \t \n", "Hello \u0001 \0 there"); + _logEvent.WriteToStringBuffer(_output); + Assert.AreEqual(" ~~ { \"key \\\\ \\\" \\t \\n\": \"Hello \\u0001 \\u0000 there\" }", _output.ToString()); + } + } +} diff --git a/src/ZeroLog.Tests/LogEventTests.Unmanaged.cs b/src/ZeroLog.Tests/LogEventTests.Unmanaged.cs index 03a255df..87842598 100644 --- a/src/ZeroLog.Tests/LogEventTests.Unmanaged.cs +++ b/src/ZeroLog.Tests/LogEventTests.Unmanaged.cs @@ -38,7 +38,7 @@ public void should_append_unmanaged() LogManager.RegisterUnmanaged(); _logEvent.AppendUnmanaged(o); - _logEvent.WriteToStringBuffer(_output); + _logEvent.WriteToStringBuffer(_output); Assert.AreEqual("1-2-3", _output.ToString()); } diff --git a/src/ZeroLog/ArgumentType.cs b/src/ZeroLog/ArgumentType.cs index e2c23942..778836c0 100644 --- a/src/ZeroLog/ArgumentType.cs +++ b/src/ZeroLog/ArgumentType.cs @@ -20,5 +20,6 @@ internal enum ArgumentType : byte Enum, Null, Unmanaged, + KeyString, } } diff --git a/src/ZeroLog/Config/JsonConfigurator.cs b/src/ZeroLog/Config/JsonConfigurator.cs index 740c5cdb..7d409ab6 100644 --- a/src/ZeroLog/Config/JsonConfigurator.cs +++ b/src/ZeroLog/Config/JsonConfigurator.cs @@ -52,6 +52,9 @@ private static void ConfigureGlobal(ZeroLogJsonConfiguration config) if (config.NullDisplayString != null) LogManager.Config.NullDisplayString = config.NullDisplayString; + + if (config.JsonSeparator != null) + LogManager.Config.JsonSeparator = config.JsonSeparator; } private static ZeroLogJsonConfiguration ConfigureResolver(string configFileFullPath, HierarchicalResolver resolver) diff --git a/src/ZeroLog/Config/ZeroLogJsonConfiguration.cs b/src/ZeroLog/Config/ZeroLogJsonConfiguration.cs index 8c252805..a32750f6 100644 --- a/src/ZeroLog/Config/ZeroLogJsonConfiguration.cs +++ b/src/ZeroLog/Config/ZeroLogJsonConfiguration.cs @@ -7,6 +7,7 @@ public class ZeroLogJsonConfiguration : IHierarchicalConfiguration public int LogEventArgumentCapacity { get; set; } public bool LazyRegisterEnums { get; set; } public string? NullDisplayString { get; set; } + public string? JsonSeparator { get; set; } public AppenderDefinition[] Appenders { get; set; } = new AppenderDefinition[0]; diff --git a/src/ZeroLog/EnumArg.cs b/src/ZeroLog/EnumArg.cs index bac13649..16c2da3f 100644 --- a/src/ZeroLog/EnumArg.cs +++ b/src/ZeroLog/EnumArg.cs @@ -23,34 +23,17 @@ public EnumArg(IntPtr typeHandle, ulong value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendTo(StringBuffer stringBuffer) { - var enumString = EnumCache.GetString(_typeHandle, _value, out var enumRegistered); + var enumString = GetString(); + if (enumString != null) - { stringBuffer.Append(enumString); - return; - } - - AppendToSlow(stringBuffer, enumRegistered); + else + AppendNumericValue(stringBuffer); } [MethodImpl(MethodImplOptions.NoInlining)] - private void AppendToSlow(StringBuffer stringBuffer, bool enumRegistered) + public void AppendNumericValue(StringBuffer stringBuffer) { - if (!enumRegistered && LogManager.Config.LazyRegisterEnums) - { - var type = TypeUtil.GetTypeFromHandle(_typeHandle); - if (type is null) - return; - - LogManager.RegisterEnum(type); - var enumString = EnumCache.GetString(_typeHandle, _value, out _); - if (enumString != null) - { - stringBuffer.Append(enumString); - return; - } - } - if (_value <= long.MaxValue) { stringBuffer.Append(_value, StringView.Empty); @@ -62,5 +45,24 @@ private void AppendToSlow(StringBuffer stringBuffer, bool enumRegistered) else stringBuffer.Append(_value, StringView.Empty); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string? GetString() + => EnumCache.GetString(_typeHandle, _value, out var enumRegistered) + ?? GetStringSlow(enumRegistered); + + [MethodImpl(MethodImplOptions.NoInlining)] + private string? GetStringSlow(bool enumRegistered) + { + if (enumRegistered || !LogManager.Config.LazyRegisterEnums) + return null; + + var type = TypeUtil.GetTypeFromHandle(_typeHandle); + if (type is null) + return null; + + LogManager.RegisterEnum(type); + return EnumCache.GetString(_typeHandle, _value, out _); + } } } diff --git a/src/ZeroLog/ForwardingLogEvent.cs b/src/ZeroLog/ForwardingLogEvent.cs index 3dd5fbd4..bb464e20 100644 --- a/src/ZeroLog/ForwardingLogEvent.cs +++ b/src/ZeroLog/ForwardingLogEvent.cs @@ -1,6 +1,5 @@ using System; using System.Text.Formatting; -using JetBrains.Annotations; using ZeroLog.Appenders; namespace ZeroLog @@ -39,6 +38,19 @@ public void AppendGeneric(T arg) public unsafe ILogEvent AppendAsciiString(byte* bytes, int length) => this; public ILogEvent AppendAsciiString(ReadOnlySpan bytes) => this; public ILogEvent AppendAsciiString(ReadOnlySpan chars) => this; + public ILogEvent AppendKeyValue(string key, string? value) => this; + + public ILogEvent AppendKeyValue(string key, T value) + where T : struct, Enum + { + return this; + } + + public ILogEvent AppendKeyValue(string key, T? value) + where T : struct, Enum + { + return this; + } public ILogEvent AppendEnum(T value) where T : struct, Enum @@ -57,7 +69,7 @@ public void Log() _log.Enqueue(_logEventToAppend); } - public void WriteToStringBuffer(StringBuffer stringBuffer) + public void WriteToStringBuffer(StringBuffer stringBuffer, KeyValuePointerBuffer keyValuePointerBuffer) { } diff --git a/src/ZeroLog/IInternalLogEvent.cs b/src/ZeroLog/IInternalLogEvent.cs index 450657aa..7854a824 100644 --- a/src/ZeroLog/IInternalLogEvent.cs +++ b/src/ZeroLog/IInternalLogEvent.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.Formatting; namespace ZeroLog @@ -9,7 +10,7 @@ internal interface IInternalLogEvent : ILogEvent void SetTimestamp(DateTime timestamp); void AppendFormat(string format); void AppendGeneric(T arg); - void WriteToStringBuffer(StringBuffer stringBuffer); + void WriteToStringBuffer(StringBuffer stringBuffer, KeyValuePointerBuffer keyValuePointerBuffer); void WriteToStringBufferUnformatted(StringBuffer stringBuffer); bool IsPooled { get; } } diff --git a/src/ZeroLog/ILogEvent.cs b/src/ZeroLog/ILogEvent.cs index 13f03f56..d81543ec 100644 --- a/src/ZeroLog/ILogEvent.cs +++ b/src/ZeroLog/ILogEvent.cs @@ -19,6 +19,14 @@ ILogEvent AppendEnum(T value) ILogEvent AppendEnum(T? value) where T : struct, Enum; + ILogEvent AppendKeyValue(string key, string? value); + + ILogEvent AppendKeyValue(string key, T value) + where T : struct, Enum; + + ILogEvent AppendKeyValue(string key, T? value) + where T : struct, Enum; + void Log(); } } diff --git a/src/ZeroLog/JsonWriter.cs b/src/ZeroLog/JsonWriter.cs new file mode 100644 index 00000000..cd9ac07c --- /dev/null +++ b/src/ZeroLog/JsonWriter.cs @@ -0,0 +1,212 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text.Formatting; +using ZeroLog.Utils; + +namespace ZeroLog +{ + internal static unsafe class JsonWriter + { + public static void WriteJsonToStringBuffer(StringBuffer stringBuffer, KeyValuePointerBuffer keyValuePointerBuffer, string[] strings) + { + stringBuffer.Append(LogManager.Config.JsonSeparator); + stringBuffer.Append("{ "); + + for (var i = 0; i < keyValuePointerBuffer.KeyPointerCount; i++) + { + if (i != 0) + stringBuffer.Append(", "); + + var dataPointer = keyValuePointerBuffer.GetKeyPointer(i); + + // Key. + AppendJsonValue(stringBuffer, strings, ref dataPointer); + + stringBuffer.Append(": "); + + // Value. + AppendJsonValue(stringBuffer, strings, ref dataPointer); + } + + stringBuffer.Append(" }"); + } + + private static void AppendJsonValue(StringBuffer stringBuffer, string[] strings, ref byte* dataPointer) + { + var argumentType = (ArgumentType)(*dataPointer & ArgumentTypeMask.ArgumentType); + dataPointer += sizeof(ArgumentType); + + switch (argumentType) + { + case ArgumentType.KeyString: + case ArgumentType.String: + AppendString(strings[*dataPointer], stringBuffer); + dataPointer += sizeof(byte); + break; + + case ArgumentType.AsciiString: + var length = *(int*)dataPointer; + dataPointer += sizeof(int); + stringBuffer.Append('"'); + + for (var i = 0; i < length; ++i) + AppendEscapedChar((char)*(dataPointer + i), stringBuffer); + + stringBuffer.Append('"'); + dataPointer += length; + break; + + case ArgumentType.Boolean: + stringBuffer.Append(*(bool*)dataPointer ? "true" : "false"); + dataPointer += sizeof(bool); + break; + + case ArgumentType.Byte: + stringBuffer.Append(*dataPointer, StringView.Empty); + dataPointer += sizeof(byte); + break; + + case ArgumentType.Char: + stringBuffer.Append('"'); + AppendEscapedChar(*(char*)dataPointer, stringBuffer); + stringBuffer.Append('"'); + + dataPointer += sizeof(char); + break; + + case ArgumentType.Int16: + stringBuffer.Append(*(short*)dataPointer, StringView.Empty); + dataPointer += sizeof(short); + break; + + case ArgumentType.Int32: + stringBuffer.Append(*(int*)dataPointer, StringView.Empty); + dataPointer += sizeof(int); + break; + + case ArgumentType.Int64: + stringBuffer.Append(*(long*)dataPointer, StringView.Empty); + dataPointer += sizeof(long); + break; + + case ArgumentType.Single: + stringBuffer.Append(*(float*)dataPointer, StringView.Empty); + dataPointer += sizeof(float); + break; + + case ArgumentType.Double: + stringBuffer.Append(*(double*)dataPointer, StringView.Empty); + dataPointer += sizeof(double); + break; + + case ArgumentType.Decimal: + stringBuffer.Append(*(decimal*)dataPointer, StringView.Empty); + dataPointer += sizeof(decimal); + break; + + case ArgumentType.Guid: + stringBuffer.Append('"'); + stringBuffer.Append(*(Guid*)dataPointer, StringView.Empty); + stringBuffer.Append('"'); + dataPointer += sizeof(Guid); + break; + + case ArgumentType.DateTime: + stringBuffer.Append('"'); + stringBuffer.Append(*(DateTime*)dataPointer, StringView.Empty); + stringBuffer.Append('"'); + dataPointer += sizeof(DateTime); + break; + + case ArgumentType.TimeSpan: + stringBuffer.Append('"'); + stringBuffer.Append(*(TimeSpan*)dataPointer, StringView.Empty); + stringBuffer.Append('"'); + dataPointer += sizeof(TimeSpan); + break; + + case ArgumentType.Enum: + var enumArg = (EnumArg*)dataPointer; + var enumString = enumArg->GetString(); + + if (enumString != null) + AppendString(enumString, stringBuffer); + else + enumArg->AppendNumericValue(stringBuffer); + + dataPointer += sizeof(EnumArg); + break; + + case ArgumentType.Null: + stringBuffer.Append("null"); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendString(string value, StringBuffer stringBuffer) + { + stringBuffer.Append('"'); + + foreach (var c in value) + AppendEscapedChar(c, stringBuffer); + + stringBuffer.Append('"'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AppendEscapedChar(char c, StringBuffer stringBuffer) + { + // Escape characters based on https://tools.ietf.org/html/rfc7159 + + if (c == '\\' || c == '"' || c <= '\u001F') + AppendControlChar(c, stringBuffer); + else + stringBuffer.Append(c); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AppendControlChar(char c, StringBuffer stringBuffer) + { + switch (c) + { + case '"': + stringBuffer.Append(@"\"""); + break; + + case '\\': + stringBuffer.Append(@"\\"); + break; + + case '\b': + stringBuffer.Append(@"\b"); + break; + + case '\t': + stringBuffer.Append(@"\t"); + break; + + case '\n': + stringBuffer.Append(@"\n"); + break; + + case '\f': + stringBuffer.Append(@"\f"); + break; + + case '\r': + stringBuffer.Append(@"\r"); + break; + + default: + stringBuffer.Append(@"\u00"); + var byteValue = unchecked((byte)c); + HexUtils.AppendValueAsHex(stringBuffer, &byteValue, 1); + break; + } + } + } +} diff --git a/src/ZeroLog/KeyValuePointerBuffer.cs b/src/ZeroLog/KeyValuePointerBuffer.cs new file mode 100644 index 00000000..d42e3761 --- /dev/null +++ b/src/ZeroLog/KeyValuePointerBuffer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace ZeroLog +{ + internal class KeyValuePointerBuffer + { + private readonly List _keyPointers = new List(byte.MaxValue); + + public int KeyPointerCount => _keyPointers.Count; + + public unsafe byte* GetKeyPointer(int index) + => (byte*)_keyPointers[index].ToPointer(); + + public unsafe void AddKeyPointer(byte* pointer) + => _keyPointers.Add(new IntPtr(pointer)); + + public void Clear() + => _keyPointers.Clear(); + } +} diff --git a/src/ZeroLog/LogEvent.Append.cs b/src/ZeroLog/LogEvent.Append.cs index bed9a915..99f3e97c 100644 --- a/src/ZeroLog/LogEvent.Append.cs +++ b/src/ZeroLog/LogEvent.Append.cs @@ -16,46 +16,70 @@ partial interface ILogEvent { ILogEvent Append(bool value); ILogEvent Append(bool? value); + ILogEvent AppendKeyValue(string key, bool value); + ILogEvent AppendKeyValue(string key, bool? value); ILogEvent Append(byte value); ILogEvent Append(byte? value); + ILogEvent AppendKeyValue(string key, byte value); + ILogEvent AppendKeyValue(string key, byte? value); ILogEvent Append(byte value, string format); ILogEvent Append(byte? value, string format); ILogEvent Append(char value); ILogEvent Append(char? value); + ILogEvent AppendKeyValue(string key, char value); + ILogEvent AppendKeyValue(string key, char? value); ILogEvent Append(short value); ILogEvent Append(short? value); + ILogEvent AppendKeyValue(string key, short value); + ILogEvent AppendKeyValue(string key, short? value); ILogEvent Append(short value, string format); ILogEvent Append(short? value, string format); ILogEvent Append(int value); ILogEvent Append(int? value); + ILogEvent AppendKeyValue(string key, int value); + ILogEvent AppendKeyValue(string key, int? value); ILogEvent Append(int value, string format); ILogEvent Append(int? value, string format); ILogEvent Append(long value); ILogEvent Append(long? value); + ILogEvent AppendKeyValue(string key, long value); + ILogEvent AppendKeyValue(string key, long? value); ILogEvent Append(long value, string format); ILogEvent Append(long? value, string format); ILogEvent Append(float value); ILogEvent Append(float? value); + ILogEvent AppendKeyValue(string key, float value); + ILogEvent AppendKeyValue(string key, float? value); ILogEvent Append(float value, string format); ILogEvent Append(float? value, string format); ILogEvent Append(double value); ILogEvent Append(double? value); + ILogEvent AppendKeyValue(string key, double value); + ILogEvent AppendKeyValue(string key, double? value); ILogEvent Append(double value, string format); ILogEvent Append(double? value, string format); ILogEvent Append(decimal value); ILogEvent Append(decimal? value); + ILogEvent AppendKeyValue(string key, decimal value); + ILogEvent AppendKeyValue(string key, decimal? value); ILogEvent Append(decimal value, string format); ILogEvent Append(decimal? value, string format); ILogEvent Append(Guid value); ILogEvent Append(Guid? value); + ILogEvent AppendKeyValue(string key, Guid value); + ILogEvent AppendKeyValue(string key, Guid? value); ILogEvent Append(Guid value, string format); ILogEvent Append(Guid? value, string format); ILogEvent Append(DateTime value); ILogEvent Append(DateTime? value); + ILogEvent AppendKeyValue(string key, DateTime value); + ILogEvent AppendKeyValue(string key, DateTime? value); ILogEvent Append(DateTime value, string format); ILogEvent Append(DateTime? value, string format); ILogEvent Append(TimeSpan value); ILogEvent Append(TimeSpan? value); + ILogEvent AppendKeyValue(string key, TimeSpan value); + ILogEvent AppendKeyValue(string key, TimeSpan? value); ILogEvent Append(TimeSpan value, string format); ILogEvent Append(TimeSpan? value, string format); } @@ -132,7 +156,7 @@ public void AppendGeneric(T arg) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(bool value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(bool))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(bool), 1)) return this; AppendArgumentType(ArgumentType.Boolean); @@ -144,7 +168,7 @@ public ILogEvent Append(bool value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(bool? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(bool))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(bool), 1)) return this; if (value == null) @@ -159,10 +183,46 @@ public ILogEvent Append(bool? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, bool value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(bool), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Boolean); + *(bool*)_dataPointer = value; + _dataPointer += sizeof(bool); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, bool? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(bool), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Boolean); + *(bool*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(bool); + return this; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(byte value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte), 1)) return this; AppendArgumentType(ArgumentType.Byte); @@ -174,7 +234,7 @@ public ILogEvent Append(byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(byte? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte), 1)) return this; if (value == null) @@ -189,11 +249,46 @@ public ILogEvent Append(byte? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, byte value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(byte), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Byte); + *(byte*)_dataPointer = value; + _dataPointer += sizeof(byte); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, byte? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(byte), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Byte); + *(byte*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(byte); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(byte value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Byte); @@ -206,7 +301,7 @@ public ILogEvent Append(byte value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(byte? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte), 1)) return this; if (value == null) @@ -225,7 +320,7 @@ public ILogEvent Append(byte? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(char value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(char))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(char), 1)) return this; AppendArgumentType(ArgumentType.Char); @@ -237,7 +332,7 @@ public ILogEvent Append(char value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(char? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(char))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(char), 1)) return this; if (value == null) @@ -252,10 +347,46 @@ public ILogEvent Append(char? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, char value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(char), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Char); + *(char*)_dataPointer = value; + _dataPointer += sizeof(char); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, char? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(char), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Char); + *(char*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(char); + return this; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(short value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(short))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(short), 1)) return this; AppendArgumentType(ArgumentType.Int16); @@ -267,7 +398,7 @@ public ILogEvent Append(short value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(short? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(short))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(short), 1)) return this; if (value == null) @@ -282,11 +413,46 @@ public ILogEvent Append(short? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, short value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(short), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Int16); + *(short*)_dataPointer = value; + _dataPointer += sizeof(short); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, short? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(short), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Int16); + *(short*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(short); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(short value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(short))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(short), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Int16); @@ -299,7 +465,7 @@ public ILogEvent Append(short value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(short? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(short))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(short), 1)) return this; if (value == null) @@ -318,7 +484,7 @@ public ILogEvent Append(short? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(int value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(int))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(int), 1)) return this; AppendArgumentType(ArgumentType.Int32); @@ -330,7 +496,7 @@ public ILogEvent Append(int value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(int? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(int))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(int), 1)) return this; if (value == null) @@ -345,11 +511,46 @@ public ILogEvent Append(int? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, int value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(int), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Int32); + *(int*)_dataPointer = value; + _dataPointer += sizeof(int); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, int? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(int), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Int32); + *(int*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(int); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(int value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(int))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(int), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Int32); @@ -362,7 +563,7 @@ public ILogEvent Append(int value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(int? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(int))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(int), 1)) return this; if (value == null) @@ -381,7 +582,7 @@ public ILogEvent Append(int? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(long value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(long))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(long), 1)) return this; AppendArgumentType(ArgumentType.Int64); @@ -393,7 +594,7 @@ public ILogEvent Append(long value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(long? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(long))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(long), 1)) return this; if (value == null) @@ -408,11 +609,46 @@ public ILogEvent Append(long? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, long value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(long), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Int64); + *(long*)_dataPointer = value; + _dataPointer += sizeof(long); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, long? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(long), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Int64); + *(long*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(long); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(long value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(long))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(long), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Int64); @@ -425,7 +661,7 @@ public ILogEvent Append(long value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(long? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(long))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(long), 1)) return this; if (value == null) @@ -444,7 +680,7 @@ public ILogEvent Append(long? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(float value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(float))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(float), 1)) return this; AppendArgumentType(ArgumentType.Single); @@ -456,7 +692,7 @@ public ILogEvent Append(float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(float? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(float))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(float), 1)) return this; if (value == null) @@ -471,11 +707,46 @@ public ILogEvent Append(float? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, float value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(float), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Single); + *(float*)_dataPointer = value; + _dataPointer += sizeof(float); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, float? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(float), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Single); + *(float*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(float); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(float value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(float))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(float), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Single); @@ -488,7 +759,7 @@ public ILogEvent Append(float value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(float? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(float))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(float), 1)) return this; if (value == null) @@ -507,7 +778,7 @@ public ILogEvent Append(float? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(double value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(double))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(double), 1)) return this; AppendArgumentType(ArgumentType.Double); @@ -519,7 +790,7 @@ public ILogEvent Append(double value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(double? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(double))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(double), 1)) return this; if (value == null) @@ -534,11 +805,46 @@ public ILogEvent Append(double? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, double value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(double), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Double); + *(double*)_dataPointer = value; + _dataPointer += sizeof(double); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, double? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(double), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Double); + *(double*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(double); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(double value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(double))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(double), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Double); @@ -551,7 +857,7 @@ public ILogEvent Append(double value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(double? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(double))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(double), 1)) return this; if (value == null) @@ -570,7 +876,7 @@ public ILogEvent Append(double? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(decimal value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(decimal))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(decimal), 1)) return this; AppendArgumentType(ArgumentType.Decimal); @@ -582,7 +888,7 @@ public ILogEvent Append(decimal value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(decimal? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(decimal))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(decimal), 1)) return this; if (value == null) @@ -597,11 +903,46 @@ public ILogEvent Append(decimal? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, decimal value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(decimal), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Decimal); + *(decimal*)_dataPointer = value; + _dataPointer += sizeof(decimal); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, decimal? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(decimal), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Decimal); + *(decimal*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(decimal); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(decimal value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(decimal))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(decimal), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Decimal); @@ -614,7 +955,7 @@ public ILogEvent Append(decimal value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(decimal? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(decimal))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(decimal), 1)) return this; if (value == null) @@ -633,7 +974,7 @@ public ILogEvent Append(decimal? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(Guid value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(Guid))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(Guid), 1)) return this; AppendArgumentType(ArgumentType.Guid); @@ -645,7 +986,7 @@ public ILogEvent Append(Guid value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(Guid? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(Guid))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(Guid), 1)) return this; if (value == null) @@ -660,11 +1001,46 @@ public ILogEvent Append(Guid? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, Guid value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(Guid), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Guid); + *(Guid*)_dataPointer = value; + _dataPointer += sizeof(Guid); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, Guid? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(Guid), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Guid); + *(Guid*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(Guid); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(Guid value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(Guid))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(Guid), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Guid); @@ -677,7 +1053,7 @@ public ILogEvent Append(Guid value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(Guid? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(Guid))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(Guid), 1)) return this; if (value == null) @@ -696,7 +1072,7 @@ public ILogEvent Append(Guid? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(DateTime value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(DateTime))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(DateTime), 1)) return this; AppendArgumentType(ArgumentType.DateTime); @@ -708,7 +1084,7 @@ public ILogEvent Append(DateTime value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(DateTime? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(DateTime))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(DateTime), 1)) return this; if (value == null) @@ -723,11 +1099,46 @@ public ILogEvent Append(DateTime? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, DateTime value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(DateTime), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.DateTime); + *(DateTime*)_dataPointer = value; + _dataPointer += sizeof(DateTime); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, DateTime? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(DateTime), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.DateTime); + *(DateTime*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(DateTime); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(DateTime value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(DateTime))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(DateTime), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.DateTime); @@ -740,7 +1151,7 @@ public ILogEvent Append(DateTime value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(DateTime? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(DateTime))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(DateTime), 1)) return this; if (value == null) @@ -759,7 +1170,7 @@ public ILogEvent Append(DateTime? value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(TimeSpan value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(TimeSpan))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(TimeSpan), 1)) return this; AppendArgumentType(ArgumentType.TimeSpan); @@ -771,7 +1182,7 @@ public ILogEvent Append(TimeSpan value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(TimeSpan? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(TimeSpan))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(TimeSpan), 1)) return this; if (value == null) @@ -786,11 +1197,46 @@ public ILogEvent Append(TimeSpan? value) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, TimeSpan value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(TimeSpan), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.TimeSpan); + *(TimeSpan*)_dataPointer = value; + _dataPointer += sizeof(TimeSpan); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, TimeSpan? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(TimeSpan), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.TimeSpan); + *(TimeSpan*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(TimeSpan); + return this; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(TimeSpan value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(TimeSpan))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(TimeSpan), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.TimeSpan); @@ -803,7 +1249,7 @@ public ILogEvent Append(TimeSpan value, string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(TimeSpan? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(TimeSpan))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(TimeSpan), 1)) return this; if (value == null) @@ -825,46 +1271,70 @@ partial class NoopLogEvent { public ILogEvent Append(bool value) => this; public ILogEvent Append(bool? value) => this; + public ILogEvent AppendKeyValue(string key, bool value) => this; + public ILogEvent AppendKeyValue(string key, bool? value) => this; public ILogEvent Append(byte value) => this; public ILogEvent Append(byte? value) => this; + public ILogEvent AppendKeyValue(string key, byte value) => this; + public ILogEvent AppendKeyValue(string key, byte? value) => this; public ILogEvent Append(byte value, string format) => this; public ILogEvent Append(byte? value, string format) => this; public ILogEvent Append(char value) => this; public ILogEvent Append(char? value) => this; + public ILogEvent AppendKeyValue(string key, char value) => this; + public ILogEvent AppendKeyValue(string key, char? value) => this; public ILogEvent Append(short value) => this; public ILogEvent Append(short? value) => this; + public ILogEvent AppendKeyValue(string key, short value) => this; + public ILogEvent AppendKeyValue(string key, short? value) => this; public ILogEvent Append(short value, string format) => this; public ILogEvent Append(short? value, string format) => this; public ILogEvent Append(int value) => this; public ILogEvent Append(int? value) => this; + public ILogEvent AppendKeyValue(string key, int value) => this; + public ILogEvent AppendKeyValue(string key, int? value) => this; public ILogEvent Append(int value, string format) => this; public ILogEvent Append(int? value, string format) => this; public ILogEvent Append(long value) => this; public ILogEvent Append(long? value) => this; + public ILogEvent AppendKeyValue(string key, long value) => this; + public ILogEvent AppendKeyValue(string key, long? value) => this; public ILogEvent Append(long value, string format) => this; public ILogEvent Append(long? value, string format) => this; public ILogEvent Append(float value) => this; public ILogEvent Append(float? value) => this; + public ILogEvent AppendKeyValue(string key, float value) => this; + public ILogEvent AppendKeyValue(string key, float? value) => this; public ILogEvent Append(float value, string format) => this; public ILogEvent Append(float? value, string format) => this; public ILogEvent Append(double value) => this; public ILogEvent Append(double? value) => this; + public ILogEvent AppendKeyValue(string key, double value) => this; + public ILogEvent AppendKeyValue(string key, double? value) => this; public ILogEvent Append(double value, string format) => this; public ILogEvent Append(double? value, string format) => this; public ILogEvent Append(decimal value) => this; public ILogEvent Append(decimal? value) => this; + public ILogEvent AppendKeyValue(string key, decimal value) => this; + public ILogEvent AppendKeyValue(string key, decimal? value) => this; public ILogEvent Append(decimal value, string format) => this; public ILogEvent Append(decimal? value, string format) => this; public ILogEvent Append(Guid value) => this; public ILogEvent Append(Guid? value) => this; + public ILogEvent AppendKeyValue(string key, Guid value) => this; + public ILogEvent AppendKeyValue(string key, Guid? value) => this; public ILogEvent Append(Guid value, string format) => this; public ILogEvent Append(Guid? value, string format) => this; public ILogEvent Append(DateTime value) => this; public ILogEvent Append(DateTime? value) => this; + public ILogEvent AppendKeyValue(string key, DateTime value) => this; + public ILogEvent AppendKeyValue(string key, DateTime? value) => this; public ILogEvent Append(DateTime value, string format) => this; public ILogEvent Append(DateTime? value, string format) => this; public ILogEvent Append(TimeSpan value) => this; public ILogEvent Append(TimeSpan? value) => this; + public ILogEvent AppendKeyValue(string key, TimeSpan value) => this; + public ILogEvent AppendKeyValue(string key, TimeSpan? value) => this; public ILogEvent Append(TimeSpan value, string format) => this; public ILogEvent Append(TimeSpan? value, string format) => this; } @@ -873,46 +1343,70 @@ partial class ForwardingLogEvent { public ILogEvent Append(bool value) => this; public ILogEvent Append(bool? value) => this; + public ILogEvent AppendKeyValue(string key, bool value) => this; + public ILogEvent AppendKeyValue(string key, bool? value) => this; public ILogEvent Append(byte value) => this; public ILogEvent Append(byte? value) => this; + public ILogEvent AppendKeyValue(string key, byte value) => this; + public ILogEvent AppendKeyValue(string key, byte? value) => this; public ILogEvent Append(byte value, string format) => this; public ILogEvent Append(byte? value, string format) => this; public ILogEvent Append(char value) => this; public ILogEvent Append(char? value) => this; + public ILogEvent AppendKeyValue(string key, char value) => this; + public ILogEvent AppendKeyValue(string key, char? value) => this; public ILogEvent Append(short value) => this; public ILogEvent Append(short? value) => this; + public ILogEvent AppendKeyValue(string key, short value) => this; + public ILogEvent AppendKeyValue(string key, short? value) => this; public ILogEvent Append(short value, string format) => this; public ILogEvent Append(short? value, string format) => this; public ILogEvent Append(int value) => this; public ILogEvent Append(int? value) => this; + public ILogEvent AppendKeyValue(string key, int value) => this; + public ILogEvent AppendKeyValue(string key, int? value) => this; public ILogEvent Append(int value, string format) => this; public ILogEvent Append(int? value, string format) => this; public ILogEvent Append(long value) => this; public ILogEvent Append(long? value) => this; + public ILogEvent AppendKeyValue(string key, long value) => this; + public ILogEvent AppendKeyValue(string key, long? value) => this; public ILogEvent Append(long value, string format) => this; public ILogEvent Append(long? value, string format) => this; public ILogEvent Append(float value) => this; public ILogEvent Append(float? value) => this; + public ILogEvent AppendKeyValue(string key, float value) => this; + public ILogEvent AppendKeyValue(string key, float? value) => this; public ILogEvent Append(float value, string format) => this; public ILogEvent Append(float? value, string format) => this; public ILogEvent Append(double value) => this; public ILogEvent Append(double? value) => this; + public ILogEvent AppendKeyValue(string key, double value) => this; + public ILogEvent AppendKeyValue(string key, double? value) => this; public ILogEvent Append(double value, string format) => this; public ILogEvent Append(double? value, string format) => this; public ILogEvent Append(decimal value) => this; public ILogEvent Append(decimal? value) => this; + public ILogEvent AppendKeyValue(string key, decimal value) => this; + public ILogEvent AppendKeyValue(string key, decimal? value) => this; public ILogEvent Append(decimal value, string format) => this; public ILogEvent Append(decimal? value, string format) => this; public ILogEvent Append(Guid value) => this; public ILogEvent Append(Guid? value) => this; + public ILogEvent AppendKeyValue(string key, Guid value) => this; + public ILogEvent AppendKeyValue(string key, Guid? value) => this; public ILogEvent Append(Guid value, string format) => this; public ILogEvent Append(Guid? value, string format) => this; public ILogEvent Append(DateTime value) => this; public ILogEvent Append(DateTime? value) => this; + public ILogEvent AppendKeyValue(string key, DateTime value) => this; + public ILogEvent AppendKeyValue(string key, DateTime? value) => this; public ILogEvent Append(DateTime value, string format) => this; public ILogEvent Append(DateTime? value, string format) => this; public ILogEvent Append(TimeSpan value) => this; public ILogEvent Append(TimeSpan? value) => this; + public ILogEvent AppendKeyValue(string key, TimeSpan value) => this; + public ILogEvent AppendKeyValue(string key, TimeSpan? value) => this; public ILogEvent Append(TimeSpan value, string format) => this; public ILogEvent Append(TimeSpan? value, string format) => this; } diff --git a/src/ZeroLog/LogEvent.Append.tt b/src/ZeroLog/LogEvent.Append.tt index ecdfcfba..c2b9b4c8 100644 --- a/src/ZeroLog/LogEvent.Append.tt +++ b/src/ZeroLog/LogEvent.Append.tt @@ -43,6 +43,8 @@ namespace ZeroLog #> ILogEvent Append(<#= type.name #> value); ILogEvent Append(<#= type.name #>? value); + ILogEvent AppendKeyValue(string key, <#= type.name #> value); + ILogEvent AppendKeyValue(string key, <#= type.name #>? value); <# if (type.isFormattable) { @@ -94,7 +96,7 @@ namespace ZeroLog [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(<#= type.name #> value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(<#= type.name #>))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(<#= type.name #>), 1)) return this; AppendArgumentType(ArgumentType.<#= type.argType #>); @@ -106,7 +108,7 @@ namespace ZeroLog [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(<#= type.name #>? value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(<#= type.name #>))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(<#= type.name #>), 1)) return this; if (value == null) @@ -121,15 +123,50 @@ namespace ZeroLog return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, <#= type.name #> value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(<#= type.name #>), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.<#= type.argType #>); + *(<#= type.name #>*)_dataPointer = value; + _dataPointer += sizeof(<#= type.name #>); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, <#= type.name #>? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(<#= type.name #>), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.<#= type.argType #>); + *(<#= type.name #>*)_dataPointer = value.GetValueOrDefault(); + _dataPointer += sizeof(<#= type.name #>); + return this; + } + <# if (type.isFormattable) { #> - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(<#= type.name #> value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(<#= type.name #>))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(<#= type.name #>), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.<#= type.argType #>); @@ -142,7 +179,7 @@ namespace ZeroLog [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(<#= type.name #>? value, string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(<#= type.name #>))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(<#= type.name #>), 1)) return this; if (value == null) @@ -172,6 +209,8 @@ namespace ZeroLog #> public ILogEvent Append(<#= type.name #> value) => this; public ILogEvent Append(<#= type.name #>? value) => this; + public ILogEvent AppendKeyValue(string key, <#= type.name #> value) => this; + public ILogEvent AppendKeyValue(string key, <#= type.name #>? value) => this; <# if (type.isFormattable) { @@ -192,6 +231,8 @@ namespace ZeroLog #> public ILogEvent Append(<#= type.name #> value) => this; public ILogEvent Append(<#= type.name #>? value) => this; + public ILogEvent AppendKeyValue(string key, <#= type.name #> value) => this; + public ILogEvent AppendKeyValue(string key, <#= type.name #>? value) => this; <# if (type.isFormattable) { diff --git a/src/ZeroLog/LogEvent.AppendUnmanaged.cs b/src/ZeroLog/LogEvent.AppendUnmanaged.cs index fdb43119..e62287db 100644 --- a/src/ZeroLog/LogEvent.AppendUnmanaged.cs +++ b/src/ZeroLog/LogEvent.AppendUnmanaged.cs @@ -46,7 +46,7 @@ unsafe partial class LogEvent public ILogEvent AppendUnmanaged(T value) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; AppendArgumentType(ArgumentType.Unmanaged); @@ -61,7 +61,7 @@ public ILogEvent AppendUnmanaged(T value) public ILogEvent AppendUnmanaged(T value, string format) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Unmanaged); @@ -77,7 +77,7 @@ public ILogEvent AppendUnmanaged(T value, string format) public ILogEvent AppendUnmanaged(ref T value) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; AppendArgumentType(ArgumentType.Unmanaged); @@ -92,7 +92,7 @@ public ILogEvent AppendUnmanaged(ref T value) public ILogEvent AppendUnmanaged(ref T value, string format) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; AppendArgumentTypeWithFormat(ArgumentType.Unmanaged); @@ -108,7 +108,7 @@ public ILogEvent AppendUnmanaged(ref T value, string format) public ILogEvent AppendUnmanaged(T? value) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; if (value == null) @@ -129,7 +129,7 @@ public ILogEvent AppendUnmanaged(T? value) public ILogEvent AppendUnmanaged(T? value, string format) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; if (value == null) @@ -151,7 +151,7 @@ public ILogEvent AppendUnmanaged(T? value, string format) public ILogEvent AppendUnmanaged(ref T? value) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; if (value == null) @@ -172,7 +172,7 @@ public ILogEvent AppendUnmanaged(ref T? value) public ILogEvent AppendUnmanaged(ref T? value, string format) where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; if (value == null) diff --git a/src/ZeroLog/LogEvent.AppendUnmanaged.tt b/src/ZeroLog/LogEvent.AppendUnmanaged.tt index d3abd947..18a37388 100644 --- a/src/ZeroLog/LogEvent.AppendUnmanaged.tt +++ b/src/ZeroLog/LogEvent.AppendUnmanaged.tt @@ -53,7 +53,7 @@ namespace ZeroLog public <#= GetSignature(isNullable, isRef, isFormattable) #> where T : unmanaged { - if (!PrepareAppend(sizeof(ArgumentType)<#= isFormattable ? " + sizeof(byte)" : "" #> + sizeof(UnmanagedArgHeader) + sizeof(T))) + if (!PrepareAppend(sizeof(ArgumentType)<#= isFormattable ? " + sizeof(byte)" : "" #> + sizeof(UnmanagedArgHeader) + sizeof(T), 1)) return this; <# diff --git a/src/ZeroLog/LogEvent.cs b/src/ZeroLog/LogEvent.cs index 4e5b12d6..725719eb 100644 --- a/src/ZeroLog/LogEvent.cs +++ b/src/ZeroLog/LogEvent.cs @@ -70,7 +70,7 @@ private void AppendGenericSlow(T arg) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AppendFormat(string format) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(byte), 1)) return; AppendArgumentType(ArgumentType.FormatString); @@ -81,7 +81,7 @@ public void AppendFormat(string format) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent Append(string? s) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte), 1)) return this; if (s == null) @@ -95,12 +95,68 @@ public ILogEvent Append(string? s) return this; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ILogEvent AppendKeyValue(string key, string? value) + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(byte), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.String); + AppendString(value); + return this; + } + + public ILogEvent AppendKeyValue(string key, T value) + where T : struct, Enum + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(EnumArg), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + AppendArgumentType(ArgumentType.Enum); + *(EnumArg*)_dataPointer = new EnumArg(TypeUtil.TypeHandle, EnumCache.ToUInt64(value)); + _dataPointer += sizeof(EnumArg); + return this; + } + + public ILogEvent AppendKeyValue(string key, T? value) + where T : struct, Enum + { + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(byte) + sizeof(ArgumentType) + sizeof(EnumArg), 2)) + return this; + + AppendArgumentType(ArgumentType.KeyString); + AppendString(key); + + if (value == null) + { + AppendArgumentType(ArgumentType.Null); + return this; + } + + AppendArgumentType(ArgumentType.Enum); + *(EnumArg*)_dataPointer = new EnumArg(TypeUtil.TypeHandle, EnumCache.ToUInt64(value.GetValueOrDefault())); + _dataPointer += sizeof(EnumArg); + return this; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ILogEvent AppendAsciiString(byte[]? bytes, int length) { if (bytes == null) { - if (PrepareAppend(sizeof(ArgumentType))) + if (PrepareAppend(sizeof(ArgumentType), 1)) AppendArgumentType(ArgumentType.Null); return this; @@ -115,7 +171,7 @@ public ILogEvent AppendAsciiString(byte[]? bytes, int length) length = remainingBytes; } - if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length)) + if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length, 1)) return this; AppendArgumentType(ArgumentType.AsciiString); @@ -129,7 +185,7 @@ public ILogEvent AppendAsciiString(byte* bytes, int length) { if (bytes == null) { - if (PrepareAppend(sizeof(ArgumentType))) + if (PrepareAppend(sizeof(ArgumentType), 1)) AppendArgumentType(ArgumentType.Null); return this; @@ -144,7 +200,7 @@ public ILogEvent AppendAsciiString(byte* bytes, int length) length = remainingBytes; } - if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length)) + if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length, 1)) return this; AppendArgumentType(ArgumentType.AsciiString); @@ -167,7 +223,7 @@ public ILogEvent AppendAsciiString(ReadOnlySpan bytes) length = remainingBytes; } - if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length)) + if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length, 1)) return this; AppendArgumentType(ArgumentType.AsciiString); @@ -190,7 +246,7 @@ public ILogEvent AppendAsciiString(ReadOnlySpan chars) length = remainingBytes; } - if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length)) + if (length <= 0 || !PrepareAppend(sizeof(ArgumentType) + sizeof(int) + length, 1)) return this; AppendArgumentType(ArgumentType.AsciiString); @@ -215,7 +271,7 @@ public ILogEvent AppendEnum(T? value) { if (value == null) { - if (PrepareAppend(sizeof(ArgumentType))) + if (PrepareAppend(sizeof(ArgumentType), 1)) AppendArgumentType(ArgumentType.Null); return this; @@ -227,7 +283,7 @@ public ILogEvent AppendEnum(T? value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private ILogEvent AppendEnumInternal(T value) { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(EnumArg))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(EnumArg), 1)) return this; AppendArgumentType(ArgumentType.Enum); @@ -239,7 +295,7 @@ private ILogEvent AppendEnumInternal(T value) [MethodImpl(MethodImplOptions.NoInlining)] private void AppendNullableEnumInternal(T value) // T = Nullable { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(EnumArg))) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(EnumArg), 1)) return; var enumValue = EnumCache.ToUInt64Nullable(value); @@ -257,7 +313,7 @@ private void AppendNullableEnumInternal(T value) // T = Nullable [MethodImpl(MethodImplOptions.NoInlining)] private void AppendUnmanagedInternal(T arg) // T = unmanaged or Nullable { - if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + UnsafeTools.SizeOf())) + if (!PrepareAppend(sizeof(ArgumentType) + sizeof(UnmanagedArgHeader) + UnsafeTools.SizeOf(), 1)) return; // If T is a Nullable, we copy it as-is and let the formatter deal with it. @@ -277,22 +333,124 @@ public void Log() _log.Enqueue(this); } - public void WriteToStringBuffer(StringBuffer stringBuffer) + public void WriteToStringBuffer(StringBuffer stringBuffer, KeyValuePointerBuffer keyValuePointerBuffer) { var endOfData = _dataPointer; var dataPointer = _startOfBuffer; + keyValuePointerBuffer.Clear(); + while (dataPointer < endOfData) { - stringBuffer.Append(ref dataPointer, StringView.Empty, _strings, _argPointers, _argCount); + if (!ConsumeKeyValue(ref dataPointer, keyValuePointerBuffer)) + stringBuffer.Append(ref dataPointer, StringView.Empty, _strings, _argPointers, _argCount); } Debug.Assert(dataPointer == endOfData, "Buffer over-read"); + if (keyValuePointerBuffer.KeyPointerCount > 0) + JsonWriter.WriteJsonToStringBuffer(stringBuffer, keyValuePointerBuffer, _strings); + if (_isTruncated) stringBuffer.Append(LogManager.Config.TruncatedMessageSuffix); } + private static bool ConsumeKeyValue(ref byte* dataPointer, KeyValuePointerBuffer keyValuePointerBuffer) + { + var argumentType = (ArgumentType)(*dataPointer & ArgumentTypeMask.ArgumentType); + if (argumentType != ArgumentType.KeyString) + return false; + + // Save a pointer to the key for later when we append the Key/Value JSON. + keyValuePointerBuffer.AddKeyPointer(dataPointer); + + // Skip the key. + SkipCurrentArgument(ref dataPointer); + + // Skip the value. + SkipCurrentArgument(ref dataPointer); + + return true; + } + + private static void SkipCurrentArgument(ref byte* dataPointer) + { + var argumentType = (ArgumentType)(*dataPointer & ArgumentTypeMask.ArgumentType); + dataPointer += sizeof(ArgumentType); + + switch (argumentType) + { + case ArgumentType.String: + case ArgumentType.KeyString: + case ArgumentType.Byte: + dataPointer += sizeof(byte); + break; + + case ArgumentType.AsciiString: + var length = *(int*)dataPointer; + dataPointer += sizeof(int) + length; + break; + + case ArgumentType.Boolean: + dataPointer += sizeof(bool); + break; + + case ArgumentType.Char: + dataPointer += sizeof(char); + break; + + case ArgumentType.Int16: + dataPointer += sizeof(short); + break; + + case ArgumentType.Int32: + dataPointer += sizeof(int); + break; + + case ArgumentType.Int64: + dataPointer += sizeof(long); + break; + + case ArgumentType.Single: + dataPointer += sizeof(float); + break; + + case ArgumentType.Double: + dataPointer += sizeof(double); + break; + + case ArgumentType.Decimal: + dataPointer += sizeof(decimal); + break; + + case ArgumentType.Guid: + dataPointer += sizeof(Guid); + break; + + case ArgumentType.DateTime: + dataPointer += sizeof(DateTime); + break; + + case ArgumentType.TimeSpan: + dataPointer += sizeof(TimeSpan); + break; + + case ArgumentType.Enum: + dataPointer += sizeof(EnumArg); + break; + + case ArgumentType.Null: + break; + + case ArgumentType.FormatString: + case ArgumentType.Unmanaged: + throw new NotSupportedException($"Type is not supported {argumentType}"); + + default: + throw new ArgumentOutOfRangeException(); + } + } + public void WriteToStringBufferUnformatted(StringBuffer stringBuffer) { var endOfData = _dataPointer; @@ -326,6 +484,7 @@ private void AppendArgumentToStringBufferUnformatted(StringBuffer stringBuffer, switch (argumentType) { case ArgumentType.String: + case ArgumentType.KeyString: stringBuffer.Append('"'); stringBuffer.Append(_strings[*dataPointer]); stringBuffer.Append('"'); @@ -434,9 +593,9 @@ private void AppendArgumentToStringBufferUnformatted(StringBuffer stringBuffer, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool PrepareAppend(int requestedBytes) + private bool PrepareAppend(int requestedBytes, int requestedArgSlots) => _dataPointer + requestedBytes <= _endOfBuffer - && _argCount < _argPointers.Length + && _argCount + requestedArgSlots <= _argPointers.Length || PrepareAppendSlow(requestedBytes); [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/src/ZeroLog/LogManager.cs b/src/ZeroLog/LogManager.cs index d8a5b652..37110c6d 100644 --- a/src/ZeroLog/LogManager.cs +++ b/src/ZeroLog/LogManager.cs @@ -6,7 +6,6 @@ using System.Text; using System.Text.Formatting; using System.Threading; -using JetBrains.Annotations; using ZeroLog.Appenders; using ZeroLog.ConfigResolvers; using ZeroLog.Utils; @@ -232,11 +231,12 @@ private void WriteToAppenders() var spinWait = new SpinWait(); var stringBuffer = new StringBuffer(OutputBufferSize); var destination = new byte[OutputBufferSize]; + var keyValuePointerBuffer = new KeyValuePointerBuffer(); var flush = false; while (_isRunning || !_queue.IsEmpty) { - if (TryToProcessQueue(stringBuffer, destination)) + if (TryToProcessQueue(stringBuffer, destination, keyValuePointerBuffer)) { spinWait.Reset(); flush = true; @@ -257,7 +257,7 @@ private void WriteToAppenders() FlushAppenders(); } - private bool TryToProcessQueue(StringBuffer stringBuffer, byte[] destination) + private bool TryToProcessQueue(StringBuffer stringBuffer, byte[] destination, KeyValuePointerBuffer keyValuePointerBuffer) { if (!_queue.TryDequeue(out var logEvent)) return false; @@ -274,7 +274,8 @@ private bool TryToProcessQueue(StringBuffer stringBuffer, byte[] destination) try { - FormatLogMessage(stringBuffer, logEvent); + stringBuffer.Clear(); + logEvent.WriteToStringBuffer(stringBuffer, keyValuePointerBuffer); bytesWritten = CopyStringBufferToByteArray(stringBuffer, destination); } catch (Exception ex) @@ -324,12 +325,6 @@ private static void WriteMessageLogToAppenders(byte[] destination, IInternalLogE } } - private static void FormatLogMessage(StringBuffer stringBuffer, IInternalLogEvent logEvent) - { - stringBuffer.Clear(); - logEvent.WriteToStringBuffer(stringBuffer); - } - private static unsafe int CopyStringBufferToByteArray(StringBuffer stringBuffer, byte[] destination) { fixed (byte* dest = &destination[0]) diff --git a/src/ZeroLog/NoopLogEvent.cs b/src/ZeroLog/NoopLogEvent.cs index 5b483ba9..c891a4b8 100644 --- a/src/ZeroLog/NoopLogEvent.cs +++ b/src/ZeroLog/NoopLogEvent.cs @@ -35,6 +35,19 @@ public void AppendGeneric(T arg) public unsafe ILogEvent AppendAsciiString(byte* bytes, int length) => this; public ILogEvent AppendAsciiString(ReadOnlySpan bytes) => this; public ILogEvent AppendAsciiString(ReadOnlySpan chars) => this; + public ILogEvent AppendKeyValue(string key, string? value) => this; + + public ILogEvent AppendKeyValue(string key, T value) + where T : struct, Enum + { + return this; + } + + public ILogEvent AppendKeyValue(string key, T? value) + where T : struct, Enum + { + return this; + } public ILogEvent AppendEnum(T value) where T : struct, Enum @@ -52,7 +65,7 @@ public void Log() { } - public void WriteToStringBuffer(StringBuffer stringBuffer) + public void WriteToStringBuffer(StringBuffer stringBuffer, KeyValuePointerBuffer keyValuePointerBuffer) { } diff --git a/src/ZeroLog/ZeroLogConfig.cs b/src/ZeroLog/ZeroLogConfig.cs index 9fe70a94..c048e19b 100644 --- a/src/ZeroLog/ZeroLogConfig.cs +++ b/src/ZeroLog/ZeroLogConfig.cs @@ -1,9 +1,13 @@ -namespace ZeroLog +using System; +using JetBrains.Annotations; + +namespace ZeroLog { public class ZeroLogConfig { private string _nullDisplayString = "null"; private string _truncatedMessageSuffix = " [TRUNCATED]"; + private string _jsonSeparator = " ~~ "; public bool LazyRegisterEnums { get; set; } public bool FlushAppenders { get; set; } = true; @@ -20,6 +24,12 @@ public string TruncatedMessageSuffix set => _truncatedMessageSuffix = value ?? string.Empty; } + public string JsonSeparator + { + get => _jsonSeparator; + set => _jsonSeparator = value ?? string.Empty; + } + internal ZeroLogConfig() { }