From ad4643cb23cdb12f46463d64b2004111dcce6a9a Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Thu, 27 Jul 2017 17:15:38 +1000 Subject: [PATCH] Handling structured data as logcial operation stack items --- src/Essential.Diagnostics.Core/Version.txt | 2 +- .../Diagnostics/SeqTraceListener.cs | 76 ++++++++++++--- .../SeqStructuredDataTests.cs | 92 +++++++++++++++++++ 3 files changed, 154 insertions(+), 16 deletions(-) diff --git a/src/Essential.Diagnostics.Core/Version.txt b/src/Essential.Diagnostics.Core/Version.txt index f3b2549..d40b510 100644 --- a/src/Essential.Diagnostics.Core/Version.txt +++ b/src/Essential.Diagnostics.Core/Version.txt @@ -1 +1 @@ -2.1.720.0 +2.1.727.0 diff --git a/src/Essential.Diagnostics.SeqTraceListener/Diagnostics/SeqTraceListener.cs b/src/Essential.Diagnostics.SeqTraceListener/Diagnostics/SeqTraceListener.cs index 73709d0..1e0a442 100644 --- a/src/Essential.Diagnostics.SeqTraceListener/Diagnostics/SeqTraceListener.cs +++ b/src/Essential.Diagnostics.SeqTraceListener/Diagnostics/SeqTraceListener.cs @@ -45,6 +45,7 @@ public class SeqTraceListener : TraceListenerBase "maxQueueSize", "MaxQueueSize", "maxqueuesize", "maxRetries", "MaxRetries", "maxretries", "processDictionaryData", "ProcessDictionaryData", "processdictionarydata", + "processDictionaryLogicalOperationStack", "ProcessDictionaryLogicalOperationStack", "processdictionarylogicaloperationstack", }; /// @@ -296,6 +297,26 @@ public bool ProcessDictionaryData } } + /// + /// Gets or sets whether logical operation stack items of type IDictionary<string,object> are treated as structured data. Default is true. + /// + public bool ProcessDictionaryLogicalOperationStack + { + get + { + var processDictionaryLogicalOperationStack = true; + if (Attributes.ContainsKey("processDictionaryLogicalOperationStack")) + { + bool.TryParse(Attributes["processDictionaryLogicalOperationStack"], out processDictionaryLogicalOperationStack); + } + return processDictionaryLogicalOperationStack; + } + set + { + Attributes["processDictionaryLogicalOperationStack"] = value.ToString(CultureInfo.InvariantCulture); + } + } + /// /// Allowed attributes for this trace listener. /// @@ -341,20 +362,6 @@ private void AddAttributeProperties(Dictionary properties, Trace } } - if (_propertyLogicalOperationStack || (TraceOutputOptions & TraceOptions.LogicalOperationStack) == TraceOptions.LogicalOperationStack) - { - var stack = (eventCache != null ? eventCache.LogicalOperationStack : null) ?? Trace.CorrelationManager.LogicalOperationStack; - var logicalOperationStack = new List(); - if (stack != null && stack.Count > 0) - { - foreach (object stackItem in stack) - { - logicalOperationStack.Add(GetRecordedValue(stackItem)); - } - properties.Add("LogicalOperationStack", logicalOperationStack.ToArray()); - } - } - if (_propertyProcessId || (TraceOutputOptions & TraceOptions.ProcessId) == TraceOptions.ProcessId) { var processId = eventCache != null ? eventCache.ProcessId : 0; @@ -388,6 +395,45 @@ private void AddAttributeProperties(Dictionary properties, Trace } } + private void AddLogicalStack(Dictionary properties, TraceEventCache eventCache) + { + EnsureAttributesParsed(); + + var stack = (eventCache != null ? eventCache.LogicalOperationStack : null) ?? Trace.CorrelationManager.LogicalOperationStack; + if (stack != null && stack.Count > 0) + { + var recordStack = _propertyLogicalOperationStack || (TraceOutputOptions & TraceOptions.LogicalOperationStack) == TraceOptions.LogicalOperationStack; + List logicalOperationStack = null; + if (recordStack) + { + logicalOperationStack = new List(); + } + foreach (object stackItem in stack) + { + if ((stackItem is IStructuredData) || + (stackItem is IDictionary && ProcessDictionaryLogicalOperationStack)) + { + var stackItemDictionary = (IDictionary)stackItem; + foreach (var kvp in stackItemDictionary) + { + if (kvp.Key != StructuredData.MessageTemplateProperty) + { + properties[kvp.Key] = kvp.Value; + } + } + } + if (recordStack) + { + logicalOperationStack.Add(GetRecordedValue(stackItem)); + } + } + if (recordStack) + { + properties.Add("LogicalOperationStack", logicalOperationStack.ToArray()); + } + } + } + private void AddStructuredData(Dictionary properties, IDictionary structuredData, ref Exception exception, ref string messageFormat) { foreach (var kvp in structuredData) @@ -470,8 +516,8 @@ private TraceData CreateTraceData(TraceEventCache eventCache, string source, Tra // Properties var properties = new Dictionary(); - AddAttributeProperties(properties, eventCache); + AddLogicalStack(properties, eventCache); object[] recordedArgsArray = null; var exception = default(Exception); diff --git a/src/Tests/Essential.Diagnostics.SeqTraceListener.Tests/SeqStructuredDataTests.cs b/src/Tests/Essential.Diagnostics.SeqTraceListener.Tests/SeqStructuredDataTests.cs index 6b470a7..9e7c147 100644 --- a/src/Tests/Essential.Diagnostics.SeqTraceListener.Tests/SeqStructuredDataTests.cs +++ b/src/Tests/Essential.Diagnostics.SeqTraceListener.Tests/SeqStructuredDataTests.cs @@ -350,6 +350,98 @@ public void SeqIgnoresStructuredDictionaryWhenTurnedOff() StringAssert.Matches(requestBody, regexData); } + [TestMethod] + public void SeqHandlesStructuredOperationAsTraceOutput() + { + var mockRequestFactory = new MockHttpWebRequestFactory(); + mockRequestFactory.ResponseQueue.Enqueue( + new MockHttpWebResponse(HttpStatusCode.OK, null) + ); + + var listener = new SeqTraceListener("http://testuri"); + listener.BatchSize = 0; + listener.BatchSender.HttpWebRequestFactory = mockRequestFactory; + listener.TraceOutputOptions = TraceOptions.LogicalOperationStack; + + var structuredData = new StructuredData(new Dictionary() { { "a", 1 } }); + Trace.CorrelationManager.StartLogicalOperation(structuredData); + listener.TraceEvent(null, "TestSource", TraceEventType.Warning, 1, "x{0}", "y"); + Trace.CorrelationManager.StopLogicalOperation(); + + Assert.AreEqual(1, mockRequestFactory.RequestsCreated.Count); + + var request = mockRequestFactory.RequestsCreated[0]; + var requestBody = request.RequestBody; + Console.WriteLine(requestBody); + StringAssert.Contains(requestBody, "\"MessageTemplate\":\"x{0}\""); + StringAssert.Contains(requestBody, "\"0\":\"y\""); + StringAssert.Contains(requestBody, "\"LogicalOperationStack\":[{\"a\":1}]"); + var regexData = new Regex("\"Data\":"); + StringAssert.DoesNotMatch(requestBody, regexData); + } + + [TestMethod] + public void SeqHandlesStructuredOperationAsPropertiesOnly() + { + var mockRequestFactory = new MockHttpWebRequestFactory(); + mockRequestFactory.ResponseQueue.Enqueue( + new MockHttpWebResponse(HttpStatusCode.OK, null) + ); + + var listener = new SeqTraceListener("http://testuri"); + listener.BatchSize = 0; + listener.BatchSender.HttpWebRequestFactory = mockRequestFactory; + + var structuredData = new StructuredData(new Dictionary() { { "a", 1 } }); + Trace.CorrelationManager.StartLogicalOperation(structuredData); + listener.TraceEvent(null, "TestSource", TraceEventType.Warning, 1, "x{0}", "y"); + Trace.CorrelationManager.StopLogicalOperation(); + + Assert.AreEqual(1, mockRequestFactory.RequestsCreated.Count); + + var request = mockRequestFactory.RequestsCreated[0]; + var requestBody = request.RequestBody; + Console.WriteLine(requestBody); + StringAssert.Contains(requestBody, "\"MessageTemplate\":\"x{0}\""); + StringAssert.Contains(requestBody, "\"0\":\"y\""); + StringAssert.Contains(requestBody, "\"a\":1"); + var regexData = new Regex("\"Data\":"); + StringAssert.DoesNotMatch(requestBody, regexData); + var regexStack = new Regex("\"LogicalOperationStack\":"); + StringAssert.DoesNotMatch(requestBody, regexStack); + } + + [TestMethod] + public void SeqHandlesDictionaryOperationAsProperties() + { + var mockRequestFactory = new MockHttpWebRequestFactory(); + mockRequestFactory.ResponseQueue.Enqueue( + new MockHttpWebResponse(HttpStatusCode.OK, null) + ); + + var listener = new SeqTraceListener("http://testuri"); + listener.BatchSize = 0; + listener.BatchSender.HttpWebRequestFactory = mockRequestFactory; + + var dictionaryData = new Dictionary() { { "a", 1 } }; + Trace.CorrelationManager.StartLogicalOperation(dictionaryData); + listener.TraceEvent(null, "TestSource", TraceEventType.Warning, 1, "x{0}", "y"); + Trace.CorrelationManager.StopLogicalOperation(); + + Assert.AreEqual(1, mockRequestFactory.RequestsCreated.Count); + + var request = mockRequestFactory.RequestsCreated[0]; + var requestBody = request.RequestBody; + Console.WriteLine(requestBody); + StringAssert.Contains(requestBody, "\"MessageTemplate\":\"x{0}\""); + StringAssert.Contains(requestBody, "\"0\":\"y\""); + StringAssert.Contains(requestBody, "\"a\":1"); + var regexData = new Regex("\"Data\":"); + StringAssert.DoesNotMatch(requestBody, regexData); + var regexStack = new Regex("\"LogicalOperationStack\":"); + StringAssert.DoesNotMatch(requestBody, regexStack); + } + [TestMethod] public void SeqStructuredCustomObject() {