From 5ca0ebcfc6d6e0655df5884a2a8a0664220a6d43 Mon Sep 17 00:00:00 2001 From: Dmitry-Matveev Date: Wed, 21 Nov 2018 18:00:38 -0800 Subject: [PATCH 1/3] Intiail implementation of IExtension and unknown ITelemetry support in RichPayloadEventSource --- .../Implementation/JsonSerializerTest.cs | 2 +- .../RichPayloadEventSourceTest.cs | 61 +++++++++++++--- .../Constants.cs | 2 + .../Implementation/JsonSerializer.cs | 4 +- ...RichPayloadEventSource.TelemetryHandler.cs | 69 +++++++++++++++++++ .../Implementation/RichPayloadEventSource.cs | 67 +++++++++++++----- .../Extensibility/Implementation/Telemetry.cs | 31 +++++++++ 7 files changed, 205 insertions(+), 31 deletions(-) diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/JsonSerializerTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/JsonSerializerTest.cs index ed28992480..59f600328f 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/JsonSerializerTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/JsonSerializerTest.cs @@ -113,7 +113,7 @@ public void SerializesUnknownTelemetryIntoCustomEventWithProperties() TelemetryItem data = obj.ToObject>(); Assert.AreEqual("Microsoft.ApplicationInsights.Event", data.name); - Assert.AreEqual(JsonSerializer.EventNameForUnknownTelemetry, data.data.baseData.Name); + Assert.AreEqual(Constants.EventNameForUnknownTelemetry, data.data.baseData.Name); Assert.AreEqual("testUser", data.tags["ai.user.id"]); Assert.IsTrue(DateTimeOffset.TryParse(data.time, out DateTimeOffset testResult)); Assert.AreEqual(testTime, testResult); diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/RichPayloadEventSourceTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/RichPayloadEventSourceTest.cs index cebd161a4e..ea0c77e248 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/RichPayloadEventSourceTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/RichPayloadEventSourceTest.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Reflection; using System.Collections.ObjectModel; + using Microsoft.ApplicationInsights.TestFramework; /// /// Tests the rich payload event source tracking. @@ -81,6 +82,19 @@ public void RichPayloadEventSourceEventSentTest() (client, item) => { client.TrackEvent((EventTelemetry)item); }); } + /// + /// Tests tracking unknown implementaiton of ITelemetry. + /// + [TestMethod] + public void RichPayloadEventSourceUnknownEventSentTest() + { + this.DoTracking( + RichPayloadEventSource.Keywords.Events, + new UnknownTelemetry() { Source = "source", Name = "name", ResponseCode = "200", Success = true }, // .NET 4.5 Event Source does not process empty values + typeof(External.EventData), + (client, item) => { client.Track((UnknownTelemetry)item); }); + } + /// /// RichPayloadEventSource does not copy GlobalProperties unless it is enabled. /// @@ -424,14 +438,18 @@ private void DoTracking(EventKeywords keywords, ITelemetry item, Type dataType, item.Context.User.Id = "testUserId"; item.Context.Operation.Id = Guid.NewGuid().ToString(); + item.Extension = new MyTestExtension { myIntField = 42, myStringField = "value" }; + track(client, item); + var actualEvent = listener.Messages.FirstOrDefault(); #pragma warning disable CS0618 // Type or member is obsolete - Assert.IsTrue(item.Context.Properties.ContainsKey("globalproperty1"), "Item Properties should contain the globalproperties as its copied before serialization"); + if (!(item is UnknownTelemetry)) // Global properties are copied directly into output properties for unknown telemetry + { + Assert.IsTrue(item.Context.Properties.ContainsKey("globalproperty1"), "Item Properties should contain the globalproperties as its copied before serialization"); + } #pragma warning restore CS0618 // Type or member is obsolete - var actualEvent = listener.Messages.FirstOrDefault(); - Assert.IsNotNull(actualEvent); Assert.AreEqual(client.InstrumentationKey, actualEvent.Payload[0]); @@ -465,20 +483,43 @@ private void DoTracking(EventKeywords keywords, ITelemetry item, Type dataType, { object[] properties = (object[])((IDictionary)actualEvent.Payload[2])["properties"]; #pragma warning disable CS0618 // Type or member is obsolete - if (!(item is PerformanceCounterTelemetry)) + if (item is PerformanceCounterTelemetry) #pragma warning restore CS0618 // Type or member is obsolete { - // There should be 3 entries in properties + // There should be 6 entries in properties + // 1. from item's ISupportProperties.Properties + // 2. from item context.GlobalProperties + // 3. from item context.Properties + // 4. from myInfField in item's Extension + // 5. from myStringField in item's Extension + // 6. PerfCounter name is a custom property. + Assert.AreEqual(6, properties.Length); + } + else if (item is UnknownTelemetry) + { + // There should be 11 entries in properties, all fields are flattened into properties // 1. from item's ISupportProperties.Properties // 2. from item context.GlobalProperties - // 3. from item context.Properties - Assert.AreEqual(3, properties.Length); + // 3. from item context.Properties + // 4. from myInfField in item's Extension + // 5. from myStringField in item's Extension + // 6. Unknown Telemetry name. + // 7. Unknown Telemetry id + // 8. Unknown Telemetry responseCode + // 9. Unknown Telemetry source + // 10. Unknown Telemetry duration + // 11. Unknown Telemetry success + Assert.AreEqual(11, properties.Length); } else { - // There should be 4 entries in properties - // 4. PerfCounter name is a custom property. - Assert.AreEqual(4, properties.Length); + // There should be 5 entries in properties + // 1. from item's ISupportProperties.Properties + // 2. from item context.GlobalProperties + // 3. from item context.Properties + // 4. from myInfField in item's Extension + // 5. from myStringField in item's Extension + Assert.AreEqual(5, properties.Length); } } diff --git a/src/Microsoft.ApplicationInsights/Constants.cs b/src/Microsoft.ApplicationInsights/Constants.cs index ef2260c59a..78ed130ca1 100644 --- a/src/Microsoft.ApplicationInsights/Constants.cs +++ b/src/Microsoft.ApplicationInsights/Constants.cs @@ -10,6 +10,8 @@ internal class Constants internal const string DevModeTelemetryNamePrefix = "Microsoft.ApplicationInsights.Dev."; + internal const string EventNameForUnknownTelemetry = "ConvertedTelemetry"; + internal const int MaxExceptionCountToSave = 10; } } diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/JsonSerializer.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/JsonSerializer.cs index 2f6c175aad..358c09b13c 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/JsonSerializer.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/JsonSerializer.cs @@ -19,8 +19,6 @@ [EditorBrowsable(EditorBrowsableState.Never)] public static class JsonSerializer { - internal const string EventNameForUnknownTelemetry = "ConvertedTelemetry"; - private static readonly UTF8Encoding TransmissionEncoding = new UTF8Encoding(false); /// @@ -209,7 +207,7 @@ private static void SerializeUnknownTelemetryHelper(ITelemetry telemetryItem, Js jsonSerializationWriter.WriteStartObject("baseData"); jsonSerializationWriter.WriteProperty("ver", 2); - jsonSerializationWriter.WriteProperty("name", EventNameForUnknownTelemetry); + jsonSerializationWriter.WriteProperty("name", Constants.EventNameForUnknownTelemetry); jsonSerializationWriter.WriteProperty("properties", dictionarySerializationWriter.AccumulatedDictionary); jsonSerializationWriter.WriteProperty("measurements", dictionarySerializationWriter.AccumulatedMeasurements); diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs index 3c3b755f04..3ca3c52df1 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs @@ -199,6 +199,75 @@ private Action CreateOperationStartStopHandler( }; } + /// + /// Create handler for unknown telemetry that accepts EventData, InstrumentationKey, tags, flags + /// + private Action, long> CreateHandlerForUnknownTelemetry(EventSource eventSource) + { + var eventSourceType = eventSource.GetType(); + + // EventSource.Write (String, EventSourceOptions, T) + var writeGenericMethod = eventSourceType.GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Where(m => m.Name == "Write" && m.IsGenericMethod == true) + .Select(m => new { Method = m, Parameters = m.GetParameters() }) + .Where(m => m.Parameters.Length == 3 + && m.Parameters[0].ParameterType.FullName == "System.String" + && m.Parameters[1].ParameterType.FullName == "System.Diagnostics.Tracing.EventSourceOptions" + && m.Parameters[2].ParameterType.FullName == null && m.Parameters[2].ParameterType.IsByRef == false) + .Select(m => m.Method) + .SingleOrDefault(); + + if (writeGenericMethod == null) + { + return null; + } + + var eventSourceOptionsType = eventSourceType.Assembly.GetType("System.Diagnostics.Tracing.EventSourceOptions"); + var eventSourceOptionsKeywordsProperty = eventSourceOptionsType.GetProperty("Keywords", BindingFlags.Public | BindingFlags.Instance); + + var eventSourceOptions = Activator.CreateInstance(eventSourceOptionsType); + var keywords = Keywords.Events; + eventSourceOptionsKeywordsProperty.SetValue(eventSourceOptions, keywords); + var dummyEventData = new EventData(); + var writeMethod = writeGenericMethod.MakeGenericMethod(new + { + PartA_iKey = this.dummyPartAiKeyValue, + PartA_Tags = this.dummyPartATagsValue, + PartB_EventData = new + { + // The properties and layout should be the same as EventData_types.cs + dummyEventData.ver, + dummyEventData.name, + dummyEventData.properties, + dummyEventData.measurements + }, + PartA_flags = this.dummyPartAFlagsValue, + }.GetType()); + + return (data, iKey, tags, flags) => + { + if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords)) + { + var extendedData = new + { + // The properties and layout should be the same as the anonymous type in the above MakeGenericMethod + PartA_iKey = iKey, + PartA_Tags = tags, + PartB_EventData = new + { + data.ver, + data.name, + data.properties, + data.measurements + }, + PartA_flags = flags, + }; + + writeMethod.Invoke(eventSource, new object[] { EventTelemetry.TelemetryName, eventSourceOptions, extendedData }); + } + }; + } + /// /// Create handler for request telemetry. /// diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.cs index a4823b000c..90ce51dc47 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/RichPayloadEventSource.cs @@ -4,11 +4,10 @@ namespace Microsoft.ApplicationInsights.Extensibility.Implementation using System; using System.Collections.Generic; using System.Diagnostics.Tracing; - using System.Globalization; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; - using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.Extensibility.Implementation.External; /// /// Event Source exposes Application Insights telemetry information as ETW events. @@ -51,6 +50,7 @@ public void Process(ITelemetry item) // Sanitize, Copying global properties is to be done before calling .Data, // as Data returns a singleton instance, which won't be updated with changes made // after .Data is called. + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -69,6 +69,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as TraceTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -87,6 +88,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as EventTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -108,6 +110,7 @@ public void Process(ITelemetry item) // Sanitize, Copying global properties is to be done before calling .InternalData, // as InternalData returns a singleton instance, which won't be updated with changes made // after .InternalData is called. + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -126,6 +129,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as MetricTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -144,6 +148,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as ExceptionTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -163,6 +168,7 @@ public void Process(ITelemetry item) } var telemetryItem = (item as PerformanceCounterTelemetry).Data; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -182,6 +188,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as PageViewTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -200,6 +207,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as PageViewPerformanceTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -219,6 +227,7 @@ public void Process(ITelemetry item) } var telemetryItem = (item as SessionStateTelemetry).Data; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -237,6 +246,7 @@ public void Process(ITelemetry item) } var telemetryItem = item as AvailabilityTelemetry; + telemetryItem.FlattenIExtensionIfExists(); CopyGlobalPropertiesIfRequired(item, telemetryItem.Properties); item.Sanitize(); this.WriteEvent( @@ -249,8 +259,23 @@ public void Process(ITelemetry item) } else { - string msg = string.Format(CultureInfo.InvariantCulture, "Unknown telemetry type: {0}", item.GetType()); - CoreEventSource.Log.LogVerbose(msg); + if (!this.EventSourceInternal.IsEnabled(EventLevel.Verbose, Keywords.Events)) + { + return; + } + + item.Sanitize(); + + EventData telemetryData = item.FlattenTelemetryIntoEventData(); + telemetryData.name = Constants.EventNameForUnknownTelemetry; + + this.WriteEvent( + EventTelemetry.TelemetryName, + item.Context.InstrumentationKey, + item.Context.SanitizedTags, + telemetryData, + item.Context.Flags, + Keywords.Events); } } @@ -346,14 +371,10 @@ namespace Microsoft.ApplicationInsights.Extensibility.Implementation { using System; using System.Collections.Generic; - using System.Diagnostics.Tracing; - using System.Globalization; - using System.Linq; - using System.Reflection; + using System.Diagnostics.Tracing; - using Microsoft.ApplicationInsights.Channel; - using Microsoft.ApplicationInsights.DataContracts; - using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.Extensibility.Implementation.External; /// /// RichPayload Event Source (.Net 4.5 version) @@ -379,6 +400,9 @@ internal sealed partial class RichPayloadEventSource : IDisposable /// Handler for start/stop operations. private readonly Action operationStartStopHandler; + /// Handler for Unknown ITelemetry implementations. + private readonly Action, long> unknownTelemetryHandler; + /// /// Initializes a new instance of the RichPayloadEventSource class. /// @@ -398,6 +422,8 @@ public RichPayloadEventSource() this.telemetryHandlers = this.CreateTelemetryHandlers(this.EventSourceInternal); this.operationStartStopHandler = this.CreateOperationStartStopHandler(this.EventSourceInternal); + + this.unknownTelemetryHandler = this.CreateHandlerForUnknownTelemetry(this.EventSourceInternal); } } } @@ -415,15 +441,22 @@ public void Process(ITelemetry item) Action handler = null; var itemType = item.GetType(); - if (!this.telemetryHandlers.TryGetValue(itemType, out handler)) + if (this.telemetryHandlers.TryGetValue(itemType, out handler)) { - string msg = string.Format(CultureInfo.InvariantCulture, "Unknown telemetry type: {0}", itemType.FullName); - CoreEventSource.Log.LogVerbose(msg); - - return; + item.FlattenIExtensionIfExists(); + handler(item); } + else + { + if (this.unknownTelemetryHandler != null) + { + item.Sanitize(); + EventData telemetryData = item.FlattenTelemetryIntoEventData(); + telemetryData.name = Constants.EventNameForUnknownTelemetry; - handler(item); + this.unknownTelemetryHandler(telemetryData, item.Context.InstrumentationKey, item.Context.SanitizedTags, item.Context.Flags); + } + } } /// diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Telemetry.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Telemetry.cs index 3db4f2561d..81b05df13f 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Telemetry.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Telemetry.cs @@ -6,6 +6,7 @@ using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.Extensibility.Implementation.External; internal static class Telemetry { @@ -83,6 +84,9 @@ internal static void CopyGlobalPropertiesIfExist(this ITelemetry telemetry, IDic } } + /// + /// Flattens Extension object on ITelemetry if exists into the properties and measurements + /// internal static void FlattenIExtensionIfExists(this ITelemetry telemetry) { if (telemetry.Extension != null) @@ -109,6 +113,33 @@ internal static void FlattenIExtensionIfExists(this ITelemetry telemetry) } } + /// + /// Flattens ITelemetry object into the properties and measurements + /// + /// EventData containing flattened ITelemetry object + internal static EventData FlattenTelemetryIntoEventData(this ITelemetry telemetry) + { + EventData flatTelemetry = new EventData(); + DictionarySerializationWriter dictionarySerializationWriter = new DictionarySerializationWriter(); + telemetry.SerializeData(dictionarySerializationWriter); // Properties and Measurements are covered as part of Data if present + Utils.CopyDictionary(dictionarySerializationWriter.AccumulatedDictionary, flatTelemetry.properties); + Utils.CopyDictionary(dictionarySerializationWriter.AccumulatedMeasurements, flatTelemetry.measurements); + if (telemetry.Context.GlobalPropertiesValue != null) + { + Utils.CopyDictionary(telemetry.Context.GlobalProperties, flatTelemetry.properties); + } + + if (telemetry.Extension != null) + { + DictionarySerializationWriter extensionSerializationWriter = new DictionarySerializationWriter(); + telemetry.Extension.Serialize(extensionSerializationWriter); // Extension is supposed to be flattened as well + Utils.CopyDictionary(extensionSerializationWriter.AccumulatedDictionary, flatTelemetry.properties); + Utils.CopyDictionary(extensionSerializationWriter.AccumulatedMeasurements, flatTelemetry.measurements); + } + + return flatTelemetry; + } + /// /// Inspect if Properties contains 'DeveloperMode' and return it's boolean value. /// From bab9dd0c3e09dcc81637bdaa579dfdcc1546b9f9 Mon Sep 17 00:00:00 2001 From: Dmitry-Matveev Date: Wed, 21 Nov 2018 18:10:49 -0800 Subject: [PATCH 2/3] Changelog update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b05aaffa7..ae7feb6821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ This changelog will be used to generate documentation on [release notes page](http://azure.microsoft.com/documentation/articles/app-insights-release-notes-dotnet/). +## Version 2.9.0-beta3 +- [Flatten IExtension and Unknown ITelemetry implementations for Rich Payload Event Source consumption](https://github.com/Microsoft/ApplicationInsights-dotnet/pull/1016) + ## Version 2.9.0-beta2 - [Remove unused reference to System.Web.Extensions](https://github.com/Microsoft/ApplicationInsights-dotnet/pull/956) - [PageViewTelemetry](https://github.com/Microsoft/ApplicationInsights-dotnet/blob/8673ed1d15005713755e0bb9594acfe0ee00b869/src/Microsoft.ApplicationInsights/DataContracts/PageViewTelemetry.cs) now supports [ISupportMetrics](https://github.com/Microsoft/ApplicationInsights-dotnet/blob/39a5ef23d834777eefdd72149de705a016eb06b0/src/Microsoft.ApplicationInsights/DataContracts/ISupportMetrics.cs) From bf2fe71af976dd370e04ffcb93fe7dc029d4b16d Mon Sep 17 00:00:00 2001 From: Dmitry-Matveev Date: Mon, 26 Nov 2018 16:12:12 -0800 Subject: [PATCH 3/3] Update PR link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae7feb6821..8afebc607a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ This changelog will be used to generate documentation on [release notes page](http://azure.microsoft.com/documentation/articles/app-insights-release-notes-dotnet/). ## Version 2.9.0-beta3 -- [Flatten IExtension and Unknown ITelemetry implementations for Rich Payload Event Source consumption](https://github.com/Microsoft/ApplicationInsights-dotnet/pull/1016) +- [Flatten IExtension and Unknown ITelemetry implementations for Rich Payload Event Source consumption](https://github.com/Microsoft/ApplicationInsights-dotnet/pull/1017) ## Version 2.9.0-beta2 - [Remove unused reference to System.Web.Extensions](https://github.com/Microsoft/ApplicationInsights-dotnet/pull/956)