Last active
October 20, 2023 23:43
-
-
Save RyanThomas73/534027e37679a96ffe5688340c62c56a to your computer and use it in GitHub Desktop.
AppInsights telemetry initializer to include exception.Data information
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.ApplicationInsights.DataContracts; | |
using Microsoft.ApplicationInsights.Extensibility; | |
using System; | |
using System.COllections.Generic; | |
namespace YourDomain.ApplicationInsights.Extensibility; | |
/// <summary> | |
/// Usage Example: | |
/// <c>builder.Services.AddSingleton<ITelemetryInitializer, ExceptionDataDictionaryTelemetryInitializer>();</c> | |
/// </summary> | |
public class ExceptionDataDictionaryTelemetryInitializer : ITelemetryInitializer | |
{ | |
public void Initialize(ITelemetry telemetry) | |
{ | |
if (telemetry is not ExceptionTelemetry exceptionTelemetry | |
|| exceptionTelemetry.Exception is null) | |
{ | |
return; | |
} | |
var exceptionIndex = -1; | |
Queue<Exception>? exceptionsToCheck = null; | |
var initialException = exceptionTelemetry.Exception | |
while (initialException is not null || exceptionsToCheck?.Count > 0) | |
{ | |
++exceptionIndex; | |
var nextException = exceptionsToCheck?.Dequeue() ?? exceptionTelemetry.Exception; | |
initialException = null; | |
// NOTES: The app insights sdk currently trims the exceptions beyond a fixed count | |
// using an internal constant (currently set to 10). | |
// | |
// In an edge case where the exception tree is particularly large the exception index | |
// here would not correlate with an exception detail in the telemetry above that constant value. | |
// See https://github.com/microsoft/ApplicationInsights-dotnet/blob/a99b7d646b903df17cb6c8f6814a8bef5bc934d0/BASE/src/Microsoft.ApplicationInsights/DataContracts/ExceptionTelemetry.cs#L430 | |
// | |
// If their logic for traversing the exception tree changes, the exception indexes used here could | |
// become mismatched from the actual exception details as well. | |
SerializeExceptionData(exceptionIndex, exceptionTelemetry, nextException); | |
if (nextException is AggregateException aggregateException) | |
{ | |
exceptionsToCheck ??= new Queue<Exception>(); | |
foreach (var innerException in aggregateException.InnerExceptions) | |
exceptionsToCheck.Enqueue(innerException); | |
} | |
else if (nextException.InnerException is not null) | |
{ | |
exceptionsToCheck ??= new Queue<Exception>(); | |
exceptionsToCheck.Enqueue(nextException.InnerException); | |
} | |
} | |
} | |
public static void SerializeExceptionData( | |
int exceptionIndex, | |
ExceptionTelemetry exceptionTelemetry, | |
Exception nextException) | |
{ | |
if (nextException.Data.Count < 1) | |
return; | |
try | |
{ | |
// NOTE: Replace/customize this logic with your desired behavior for serializing the .Data dictionary | |
// and adding it to the telemetry properties | |
// e.g. JsonConvert.SerializeObject(nextException.Data); | |
IDictionaryEnumerator dataDictionaryEnumerator = nextException.Data.GetEnumerator(); | |
while (dataDictionaryEnumerator.MoveNext()) | |
{ | |
var dataKey = Convert.ToString(dataDictionaryEnumerator.Key, CultureInfo.InvariantCulture); | |
var dataValue = dataDictionaryEnumerator.Value is null | |
? "null" | |
: Convert.ToString(dataDictionaryEnumerator.Value, CultureInfo.InvariantCulture); | |
exceptionTelemetry.Properties.Add( | |
FormattableString.Invariant($"Exception[{exceptionIndex}].{dataKey}"), | |
dataValue | |
); | |
} | |
} | |
catch {} | |
} | |
} |
@pharring
Thanks much for the suggested improvements.
Note:
I tested Convert.ToString(null, CultureInfo.InvariantCulture);
(targetting .NET 6
) and it returns an empty string result not a "null"
string.
var test1 = $"A{null}"; // "A"
var test2 = FormattableString.Invariant($"B{null}"); // "B"
var test3 = Convert.ToString(null, CultureInfo.InvariantCulture); // null
object nullObject = null;
var test4 = Convert.ToString(nullObject, CultureInfo.InvariantCulture); // ""
Oh, you are right: empty string, not "null" (for the object overload of ToString). My mistake. Sorry about that.
However, you can't just add CultureInfo.InvariantCulture
on regular object.ToString
. It has to be IFormattable
(That's the other thing that Convert.ToString
does for you -- checks for IFormattable). See for example https://github.com/microsoft/ApplicationInsights-dotnet/blob/main/LOGGING/src/ILogger/ApplicationInsightsLogger.cs#L181
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Optimizations:
new Queue<Exception>
in the common case where there is no InnerException and it's not an AggregateException.Data[key]
lookup) by manually walking the Data dictionary's enumerator. It's not as convenient as the genericIDictionary<K,V>
, but just as fast:Consider using
System.Convert.ToString(..., CultureInfo.InvariantCulture)
on both the key and the value. As well as letting you specify the culture, it also handles null (returns the string "null"). Be aware that some keys/values aren't always "printable".