Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for logging structured data #40

Merged
merged 8 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/ZeroLog.Tests/LogEventTests.EdgeCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
158 changes: 158 additions & 0 deletions src/ZeroLog.Tests/LogEventTests.KeyValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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]
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());
}
}
}
1 change: 1 addition & 0 deletions src/ZeroLog/ArgumentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ internal enum ArgumentType : byte
Enum,
Null,
Unmanaged,
KeyString,
}
}
1 change: 1 addition & 0 deletions src/ZeroLog/ForwardingLogEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public void AppendGeneric<T>(T arg)
public unsafe ILogEvent AppendAsciiString(byte* bytes, int length) => this;
public ILogEvent AppendAsciiString(ReadOnlySpan<byte> bytes) => this;
public ILogEvent AppendAsciiString(ReadOnlySpan<char> chars) => this;
public ILogEvent AppendKeyValue(string key, string? value) => this;

public ILogEvent AppendEnum<T>(T value)
where T : struct, Enum
Expand Down
2 changes: 2 additions & 0 deletions src/ZeroLog/ILogEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ ILogEvent AppendEnum<T>(T value)
ILogEvent AppendEnum<T>(T? value)
where T : struct, Enum;

ILogEvent AppendKeyValue(string key, string? value);

void Log();
}
}
Loading