Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Extensible payload handler #111

Merged
merged 11 commits into from
Aug 30, 2017
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,32 @@ public void ReportsAllProperties()
}
}

[TestMethod]
[TestCategory("EventSourceListener")]
public void CustomPayloadProperties()
{
OnEventWrittenHandler onWrittenHandler = (EventWrittenEventArgs args, TelemetryClient client) =>
{
var traceTelemetry = new TraceTelemetry("CustomPayloadProperties", SeverityLevel.Verbose);
traceTelemetry.Properties.Add("CustomPayloadProperties", "true");
client.Track(traceTelemetry);
};

using (var module = new EventSourceTelemetryModule(onWrittenHandler))
{
var listeningRequest = new EventSourceListeningRequest();
listeningRequest.Name = TestEventSource.ProviderName;
module.Sources.Add(listeningRequest);

module.Initialize(GetTestTelemetryConfiguration());

TestEventSource.Default.Write("CustomPayloadProperties");

TraceTelemetry telemetry = (TraceTelemetry)this.adapterHelper.Channel.SentItems[0];
Assert.IsTrue(telemetry.Properties.All(kvp => kvp.Key.Equals("CustomPayloadProperties") && kvp.Value.Equals("true")));
}
}

// TODO: there is a known issue with EventListner that prevents it from reporting activities properly
// This problem is fixed in .NET Core 1.1. (https://github.com/dotnet/coreclr/pull/7591), but it won't be ported
// to .NET Desktop till 4.7.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace Microsoft.ApplicationInsights.EventSourceListener
using Microsoft.ApplicationInsights.TraceEvent.Shared.Implementation;
using Microsoft.ApplicationInsights.TraceEvent.Shared.Utilities;

public delegate void OnEventWrittenHandler(EventWrittenEventArgs eventArgs, TelemetryClient client);

/// <summary>
/// A module to trace data submitted via .NET framework <seealso cref="System.Diagnostics.Tracing.EventSource" /> class.
/// </summary>
Expand All @@ -27,14 +29,26 @@ public class EventSourceTelemetryModule : EventListener, ITelemetryModule
private bool initialized; // Relying on the fact that default value in .NET Framework is false
private ConcurrentQueue<EventSource> appDomainEventSources;
private ConcurrentQueue<EventSource> enabledEventSources;
private readonly OnEventWrittenHandler onEventWrittenHandler;

/// <summary>
/// Initializes a new instance of the <see cref="EventSourceTelemetryModule"/> class.
/// </summary>
public EventSourceTelemetryModule() : this(EventDataExtensions.Track)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="EventSourceTelemetryModule"/> class.
/// </summary>
public EventSourceTelemetryModule()
/// <param name="onEventWrittenHandler">Action to be executed each time an event is written to format and send via the configured <see cref="TelemetryClient"/></param>
public EventSourceTelemetryModule(OnEventWrittenHandler onEventWrittenHandler)
{
if (onEventWrittenHandler == null) throw new ArgumentNullException(nameof(onEventWrittenHandler));

this.Sources = new List<EventSourceListeningRequest>();
this.enabledEventSources = new ConcurrentQueue<EventSource>();
this.onEventWrittenHandler = onEventWrittenHandler;
}

/// <summary>
Expand Down Expand Up @@ -106,7 +120,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
// and not that useful for production tracing. However, TPL EventSource must be enabled to get hierarchical activity IDs.
if (this.initialized && !TplActivities.TplEventSourceGuid.Equals(eventData.EventSource.Guid))
{
eventData.Track(this.client);
this.onEventWrittenHandler(eventData, this.client);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put try catch around it as it is an external code you are calling

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.ApplicationInsights.EventSourceListener.Implementation
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.TraceEvent.Shared.Utilities;

internal static class EventDataExtensions
public static class EventDataExtensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why making it public? Do you want to re-use certain methods from here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the PopulateStandardProperties method is very useful for outside creators who want to write their own payload readers but make still use of the rest of the properties.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the callback may be called after telemetry got initialized then? My worry is that if you use this method you probably relying on the implementation too much. It feel you want a callback to "add" information, not to replace. Is it correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I am not the standard consumer here but it works exactly as I would expect it to work and it handles the job of that I would just end up implementing myself if it wasn't there. For my scenario, I call:

var telemetry = eventData.ToTraceTelementry()
                                          .PopulateStandardProperties(eventData);

// My custom Payload handler 
eventData.ExtractPayloadData(telemetry);

This gives me a TraceTelemetry with all of the "boilerplate" fields populated from the EventWrittenEventArgs and then I add my own payload onto that. Of course you need not call any of these methods if you don't want to, but I find them convenient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! I appreciate you taking time to submit this PR and have this conversation. It definitely helps to form the opinion on how analogous generic API should look like.

{
private static Lazy<Random> random = new Lazy<Random>();

Expand All @@ -30,30 +30,35 @@ internal static class EventDataExtensions
};

/// <summary>
/// Creates a TraceTelemetry out of an EventSource event and tracks it using the supplied client.
/// Creates a TraceTelemetry out of an EventSource event.
/// </summary>
/// <param name="eventSourceEvent">The source for the telemetry data.</param>
/// <param name="client">Client to track the data with.</param>
public static void Track(this EventWrittenEventArgs eventSourceEvent, TelemetryClient client)
public static TraceTelemetry CreateTraceTelementry(this EventWrittenEventArgs eventSourceEvent)
{
Debug.Assert(client != null, "Should always receive a valid client");

string formattedMessage = null;
if (eventSourceEvent.Message != null)
{
try
{
// If the event has a badly formatted manifest, message formatting might fail
formattedMessage = string.Format(CultureInfo.InvariantCulture, eventSourceEvent.Message, eventSourceEvent.Payload.ToArray());
formattedMessage = string.Format(CultureInfo.InvariantCulture, eventSourceEvent.Message,
eventSourceEvent.Payload.ToArray());
}
catch
{
}
}
TraceTelemetry telemetry = new TraceTelemetry(formattedMessage, eventLevelToSeverityLevel[(int)eventSourceEvent.Level]);

eventSourceEvent.ExtractPayloadData(telemetry);
return new TraceTelemetry(formattedMessage,
eventLevelToSeverityLevel[(int) eventSourceEvent.Level]);
}

/// <summary>
/// Populates a standard set of properties on the <see cref="TraceTelemetry"/> with values from the a given EventSource event.
/// </summary>
/// <param name="telemetry">Telemetry item to populate with properties.</param>
/// <param name="eventSourceEvent">Event to extract values from.</param>
public static TraceTelemetry PopulateStandardProperties(this TraceTelemetry telemetry, EventWrittenEventArgs eventSourceEvent)
{
telemetry.AddProperty(nameof(EventWrittenEventArgs.EventId), eventSourceEvent.EventId.ToString(CultureInfo.InvariantCulture));
telemetry.AddProperty(nameof(EventWrittenEventArgs.EventName), eventSourceEvent.EventName);
if (eventSourceEvent.ActivityId != default(Guid))
Expand All @@ -76,21 +81,37 @@ public static void Track(this EventWrittenEventArgs eventSourceEvent, TelemetryC
telemetry.AddProperty(nameof(EventWrittenEventArgs.Task), GetHexRepresentation((int)eventSourceEvent.Task));
}

client.Track(telemetry);
return telemetry;
}

/// <summary>
/// Extracts payload properties from a given EventSource event and populates the telemetry properties with values found.
/// Creates a TraceTelemetry out of an EventSource event and tracks it using the supplied client.
/// </summary>
/// <param name="eventSourceEvent">The source for the telemetry data.</param>
/// <param name="client">Client to track the data with.</param>
internal static void Track(this EventWrittenEventArgs eventSourceEvent, TelemetryClient client)
{
Debug.Assert(client != null, "Should always receive a valid client");

var telemetry = eventSourceEvent.CreateTraceTelementry()
.PopulatePayloadProperties(eventSourceEvent)
.PopulateStandardProperties(eventSourceEvent);

client.Track(telemetry);
}

/// <summary>
/// Populates properties on the <see cref="TraceTelemetry"/> with values from the Payload of a given EventSource event.
/// </summary>
/// <param name="eventSourceEvent">Event to extract values from.</param>
/// <param name="telemetry">Telemetry item to populate with properties.</param>
private static void ExtractPayloadData(this EventWrittenEventArgs eventSourceEvent, TraceTelemetry telemetry)
/// <param name="eventSourceEvent">Event to extract values from.</param>
public static TraceTelemetry PopulatePayloadProperties(this TraceTelemetry telemetry, EventWrittenEventArgs eventSourceEvent)
{
Debug.Assert(telemetry != null, "Should have received a valid TraceTelemetry object");

if (eventSourceEvent.Payload == null || eventSourceEvent.PayloadNames == null)
{
return;
return telemetry;
}

IDictionary<string, string> payloadData = telemetry.Properties;
Expand All @@ -105,6 +126,8 @@ private static void ExtractPayloadData(this EventWrittenEventArgs eventSourceEve
payloadData.Add(payloadNamesEnunmerator.Current, payloadEnumerator.Current.ToString());
}
}

return telemetry;
}

/// <summary>
Expand Down