From 72aa6a582d6eb7877e82a05e0c445d9b4aa5ac9b Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 11 Jan 2017 16:18:17 +1000 Subject: [PATCH] Convert `StructureValue` into `IDictionary` when possible Changed the way the fallback `ToString()` is applied so that array/dictionary/structure elements still have their essential structure when transmitted, even if their elements are not scalar (`ToRawValue()` scalarizes them). --- .../SerilogInput.cs | 39 +++++++++++++++++-- .../SerilogInputTests.cs | 21 ++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Diagnostics.EventFlow.Inputs.Serilog/SerilogInput.cs b/src/Microsoft.Diagnostics.EventFlow.Inputs.Serilog/SerilogInput.cs index 0db11220..51852696 100644 --- a/src/Microsoft.Diagnostics.EventFlow.Inputs.Serilog/SerilogInput.cs +++ b/src/Microsoft.Diagnostics.EventFlow.Inputs.Serilog/SerilogInput.cs @@ -106,7 +106,7 @@ private EventData ToEventData(LogEvent logEvent) return eventData; } - private object ToRawValue(LogEventPropertyValue logEventValue) + private static object ToRawValue(LogEventPropertyValue logEventValue) { // Special-case a few types of LogEventPropertyValue that allow us to maintain better type fidelity. // For everything else take the default string rendering as the data. @@ -119,7 +119,7 @@ private object ToRawValue(LogEventPropertyValue logEventValue) SequenceValue sequenceValue = logEventValue as SequenceValue; if (sequenceValue != null) { - object[] arrayResult = sequenceValue.Elements.OfType().Select(e => e.Value).ToArray(); + object[] arrayResult = sequenceValue.Elements.Select(e => ToRawScalar(e)).ToArray(); if (arrayResult.Length == sequenceValue.Elements.Count) { // All values extracted successfully, it is a flat array of scalars @@ -127,12 +127,32 @@ private object ToRawValue(LogEventPropertyValue logEventValue) } } + StructureValue structureValue = logEventValue as StructureValue; + if (structureValue != null) + { + IDictionary structureResult = new Dictionary(structureValue.Properties.Count); + foreach (var property in structureValue.Properties) + { + structureResult[property.Name] = ToRawScalar(property.Value); + } + + if (structureResult.Count == structureValue.Properties.Count) + { + if (structureValue.TypeTag != null) + { + structureResult["$type"] = structureValue.TypeTag; + } + + return structureResult; + } + } + DictionaryValue dictionaryValue = logEventValue as DictionaryValue; if (dictionaryValue != null) { IDictionary dictionaryResult = dictionaryValue.Elements - .Where(kvPair => kvPair.Key.Value is string && kvPair.Value is ScalarValue) - .ToDictionary(kvPair => (string)kvPair.Key.Value, kvPair => ((ScalarValue)kvPair.Value).Value); + .Where(kvPair => kvPair.Key.Value is string) + .ToDictionary(kvPair => (string)kvPair.Key.Value, kvPair => ToRawScalar(kvPair.Value)); if (dictionaryResult.Count == dictionaryValue.Elements.Count) { return dictionaryResult; @@ -142,5 +162,16 @@ private object ToRawValue(LogEventPropertyValue logEventValue) // Fall back to string rendering of the value return logEventValue.ToString(); } + + private static object ToRawScalar(LogEventPropertyValue value) + { + ScalarValue scalarValue = value as ScalarValue; + if (scalarValue != null) + { + return scalarValue.Value; + } + + return value.ToString(); + } } } diff --git a/test/Microsoft.Diagnostics.EventFlow.Inputs.Tests/SerilogInputTests.cs b/test/Microsoft.Diagnostics.EventFlow.Inputs.Tests/SerilogInputTests.cs index 619a7c0b..bc5c0ea5 100644 --- a/test/Microsoft.Diagnostics.EventFlow.Inputs.Tests/SerilogInputTests.cs +++ b/test/Microsoft.Diagnostics.EventFlow.Inputs.Tests/SerilogInputTests.cs @@ -8,6 +8,7 @@ using Moq; using Serilog; using Xunit; +using System.Collections.Generic; namespace Microsoft.Diagnostics.EventFlow.Inputs.Tests { @@ -146,5 +147,25 @@ public void HandlesDuplicatePropertyNames() Times.Exactly(2)); } } + + [Fact] + public void RepresentsStructuresAsRawDictionaries() + { + var healthReporterMock = new Mock(); + var observer = new Mock>(); + using (var serilogInput = new SerilogInput(healthReporterMock.Object)) + using (serilogInput.Subscribe(observer.Object)) + { + var logger = new LoggerConfiguration().WriteTo.Observers(events => events.Subscribe(serilogInput)).CreateLogger(); + + var structure = new { A = "alpha", B = "bravo" }; + logger.Information("Here is {@AStructure}", structure); + + observer.Verify(s => s.OnNext(It.Is(data => + ((IDictionary)data.Payload["AStructure"])["A"].Equals("alpha") && + ((IDictionary)data.Payload["AStructure"])["B"].Equals("bravo") + ))); + } + } } }