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

Send objects as-is #6

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using Vostok.Hercules.Client.Abstractions.Events;
using Vostok.Logging.Hercules.Constants;

namespace Vostok.Logging.Hercules.Tests
{
internal class HerculesTagsBuilderExtensions_Tests
internal class HerculesTagsBuilderExtensions_Exceptions_Tests
{
private HerculesTagsBuilder builder;

Expand Down Expand Up @@ -35,42 +34,5 @@ public void Should_serialize_inner_exceptions()
exceptions[1][ExceptionTagNames.Type].AsString.Should().Be(exception1.GetType().FullName);
exceptions[2][ExceptionTagNames.Type].AsString.Should().Be(exception2.GetType().FullName);
}

[Test]
public void Should_serialize_dates_in_ISO_format()
{
var dtoValue = DateTimeOffset.Now;
var dtValue = DateTime.Now;

var properties = new Dictionary<string, object>
{
["DateTimeOffset"] = dtoValue,
["DateTime"] = dtValue
};

builder.AddProperties(properties, null);

var tags = builder.BuildTags();

tags["DateTimeOffset"]?.AsString.Should().Be(dtoValue.ToString("O"));
tags["DateTime"]?.AsString.Should().Be(dtValue.ToString("O"));
}

[Test]
public void Should_filter_properties()
{
var properties = new Dictionary<string, object>
{
["p1"] = "v1",
["p2"] = "v2"
};

builder.AddProperties(properties, new[] {"p1"});

var tags = builder.BuildTags();

tags.ContainsKey("p1").Should().BeFalse();
tags["p2"]?.AsString.Should().Be("v2");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using Vostok.Hercules.Client.Abstractions.Events;

// ReSharper disable PossibleNullReferenceException

namespace Vostok.Logging.Hercules.Tests
{
internal class HerculesTagsBuilderExtensions_Tests
{
private HerculesTagsBuilder builder;

[SetUp]
public void Setup()
{
builder = new HerculesEventBuilder();
}

[Test]
public void Should_serialize_dates_in_ISO_format()
{
var dtoValue = DateTimeOffset.Now;
var dtValue = DateTime.Now;

var properties = new Dictionary<string, object>
{
["DateTimeOffset"] = dtoValue,
["DateTime"] = dtValue
};

builder.AddProperties(properties, null, null);

var tags = builder.BuildTags();

tags["DateTimeOffset"]?.AsString.Should().Be(dtoValue.ToString("O"));
tags["DateTime"]?.AsString.Should().Be(dtValue.ToString("O"));
}

[Test]
public void Should_filter_properties()
{
var properties = new Dictionary<string, object>
{
["p1"] = "v1",
["p2"] = "v2"
};

builder.AddProperties(properties, new[] {"p1"}, null);

var tags = builder.BuildTags();

tags.ContainsKey("p1").Should().BeFalse();
tags["p2"]?.AsString.Should().Be("v2");
}

[Test]
public void Should_serialize_complex_objects()
{
var properties = new Dictionary<string, object>
{
["p1"] = new Class1(),
["p2"] = new Class2()
};

builder.AddProperties(properties, null, null);

var tags = builder.BuildTags();

tags.Keys.Should().BeEquivalentTo("p1", "p2");

#region p1 checking

EnsureEqual(tags["p1"].AsContainer);

#endregion

#region p2 checking

var p2 = tags["p2"].AsContainer;

p2["SimpleValue"].AsInt.Should().Be(42);
EnsureEqual(p2["ObjectValue"].AsContainer);

p2["SimpleArray"].AsVector.AsStringList.Should().BeEquivalentTo("who", "is", "it");
EnsureEqual(p2["ObjectsArray"].AsVector.AsContainerList[0], 1);
EnsureEqual(p2["ObjectsArray"].AsVector.AsContainerList[1], 2);

p2["SimpleDictionary"].AsContainer["aa"].AsInt.Should().Be(55);
p2["SimpleDictionary"].AsContainer["bb"].AsInt.Should().Be(66);
EnsureEqual(p2["ObjectsDictionary"].AsContainer["123"].AsContainer, b:"123key");
EnsureEqual(p2["ObjectsDictionary"].AsContainer["124"].AsContainer, b:"124key");

#endregion

void EnsureEqual(HerculesTags value, int a = 11, string b = "12asdf")
{
value.Keys.Should().BeEquivalentTo("A", "B");
value["A"].AsInt.Should().Be(a);
value["B"].AsString.Should().Be(b);
}
}

public class Class1
{
public int A { get; set; } = 11;
public string B { get; set; } = "12asdf";
}

public class Class2
{
public int SimpleValue { get; set; } = 42;
public Class1 ObjectValue { get; set; } = new();

public List<string> SimpleArray { get; set; } = new() {"who", "is", "it"};
public Class1[] ObjectsArray { get; set; } = new[] {new Class1 {A = 1}, new Class1 {A = 2}};

public Dictionary<string, int> SimpleDictionary { get; set; } = new()
{
["aa"] = 55,
["bb"] = 66
};
public Dictionary<int, Class1> ObjectsDictionary { get; set; } = new()
{
[123] = new Class1 {B = "123key"},
[124] = new Class1 {B = "124key"}
};
}
}
}
2 changes: 1 addition & 1 deletion Vostok.Logging.Hercules/HerculesEventBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static IHerculesTagsBuilder AddLogEventData(
if (@event.Properties != null)
builder.AddContainer(
LogEventTagNames.Properties,
tagsBuilder => tagsBuilder.AddProperties(@event.Properties, filteredProperties));
tagsBuilder => tagsBuilder.AddProperties(@event.Properties, filteredProperties, formatProvider));

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Vostok.Commons.Formatting;
using Vostok.Hercules.Client.Abstractions.Events;
using Vostok.Logging.Hercules.Constants;
using Vostok.Logging.Hercules.Helpers;

namespace Vostok.Logging.Hercules
{
internal static class HerculesTagsBuilderExtensions
internal static class HerculesTagsBuilderExtensions_Exceptions
{
public static IHerculesTagsBuilder AddProperties(
this IHerculesTagsBuilder builder,
IReadOnlyDictionary<string, object> properties,
IReadOnlyCollection<string> filteredProperties)
{
foreach (var keyValuePair in properties)
{
if (IsPositionalName(keyValuePair.Key))
continue;

if (filteredProperties?.Contains(keyValuePair.Key) == true)
continue;

if (builder.TryAddObject(keyValuePair.Key, keyValuePair.Value))
continue;

var value = keyValuePair.Value;
var format = value is DateTime || value is DateTimeOffset ? "O" : null;

builder.AddValue(keyValuePair.Key, ObjectValueFormatter.Format(value, format));
}

return builder;
}

public static IHerculesTagsBuilder AddExceptionData(
this IHerculesTagsBuilder builder,
Exception exception)
Expand Down Expand Up @@ -90,20 +62,5 @@ private static void AddStackFrameData(this IHerculesTagsBuilder builder, StackFr
if (columnNumber > 0)
builder.AddValue(StackFrameTagNames.Column, columnNumber);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsPositionalName(string propertyName)
{
foreach (var character in propertyName)
{
if (character < '0')
return false;

if (character > '9')
return false;
}

return true;
}
}
}
112 changes: 112 additions & 0 deletions Vostok.Logging.Hercules/HerculesTagsBuilderExtensions_Properties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Vostok.Commons.Formatting;
using Vostok.Hercules.Client.Abstractions.Events;

namespace Vostok.Logging.Hercules
{
internal static class HerculesTagsBuilderExtensions_Properties
{
private const int MaximumRecursionDepth = 10;

public static IHerculesTagsBuilder AddProperties(
this IHerculesTagsBuilder builder,
IReadOnlyDictionary<string, object> properties,
IReadOnlyCollection<string> filteredProperties,
IFormatProvider formatProvider)
{
foreach (var kvp in properties)
{
var (key, value) = (kvp.Key, kvp.Value);

if (IsPositionalName(key))
continue;

if (filteredProperties?.Contains(key) == true)
continue;

AddProperty(builder, key, value, formatProvider, 0);
}

return builder;
}

private static void AddProperty(
this IHerculesTagsBuilder builder,
string key,
object value,
IFormatProvider formatProvider,
int depth)
{
Type valueType;

if (builder.TryAddObject(key, value) || depth > MaximumRecursionDepth)
{
// return;
}
else if (value is IFormattable formattable)
{
var format = value is DateTime or DateTimeOffset ? "O" : null;
builder.AddValue(key, formattable.ToString(format, formatProvider ?? CultureInfo.InvariantCulture));
}
else if (false) // todo (kungurtsev, 20.02.2022): check should not deconstruct
{
builder.AddValue(key, ObjectValueFormatter.Format(value, null, formatProvider));
}
else if (DictionaryInspector.IsSimpleDictionary(valueType = value.GetType()))
{
builder.AddContainer(key,
tagsBuilder =>
{
foreach (var (pKey, pValue) in DictionaryInspector.EnumerateSimpleDictionary(value))
tagsBuilder.AddProperty(pKey, pValue, formatProvider, depth + 1);
});
}
else if (value is IEnumerable enumerable)
{
builder.AddVectorOfContainers(key,
enumerable.Cast<object>().ToList(),
(tagsBuilder, element) =>
{
if (element != null && ObjectPropertiesExtractor.HasProperties(element.GetType()))
{
foreach (var (eKey, eValue) in ObjectPropertiesExtractor.ExtractProperties(element))
tagsBuilder.AddProperty(eKey, eValue, formatProvider, depth + 1);
}
else
{
tagsBuilder.AddProperty("key", element, formatProvider, depth + 1);
}
});
}
else if (ObjectPropertiesExtractor.HasProperties(valueType)) // todo (kungurtsev, 20.02.2022): or check here?
{
builder.AddContainer(key,
tagsBuilder =>
{
foreach (var (pKey, pValue) in ObjectPropertiesExtractor.ExtractProperties(value))
tagsBuilder.AddProperty(pKey, pValue, formatProvider, depth + 1);
});
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsPositionalName(string propertyName)
{
foreach (var character in propertyName)
{
if (character < '0')
return false;

if (character > '9')
return false;
}

return true;
}
}
}