Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
namespace Microsoft.ApplicationInsights
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenTelemetry;
using OpenTelemetry.Logs;

[TestClass]
public class TelemetryClientTest
{
private List<LogRecord> logItems;
private TelemetryClient telemetryClient;

[TestInitialize]
public void TestInitialize()
{
var configuration = new TelemetryConfiguration();
this.logItems = new List<LogRecord>();
configuration.InstrumentationKey = Guid.NewGuid().ToString();
configuration.ConnectionString = "InstrumentationKey=" + configuration.InstrumentationKey;
configuration.ConfigureOpenTelemetryBuilder(b => b.WithLogging(l => l.AddInMemoryExporter(logItems)));
this.telemetryClient = new TelemetryClient(configuration);
}

#region TrackException

[TestMethod]
public void TrackExceptionSendsExceptionTelemetryWithSpecifiedNameToProvideSimplestWayOfSendingExceptionTelemetry()
{
Exception ex = new Exception("Test exception message");
this.telemetryClient.TrackException(ex);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];
Assert.IsNotNull(logRecord.Exception);
Assert.AreSame(ex, logRecord.Exception);
Assert.AreEqual(LogLevel.Error, logRecord.LogLevel);
}

[TestMethod]
public void TrackExceptionWillUseRequiredFieldAsTextForTheExceptionNameWhenTheExceptionNameIsEmptyToHideUserErrors()
{
this.telemetryClient.TrackException((Exception)null);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];
Assert.IsNotNull(logRecord.Exception);
Assert.AreEqual("n/a", logRecord.Exception.Message);
}

[TestMethod]
public void TrackExceptionSendsExceptionTelemetryWithSpecifiedObjectTelemetry()
{
Exception ex = new Exception("Test telemetry exception");
this.telemetryClient.TrackException(new ExceptionTelemetry(ex));

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];
Assert.IsNotNull(logRecord.Exception);
Assert.AreEqual("Test telemetry exception", logRecord.Exception.Message);
}

[TestMethod]
public void TrackExceptionWillUseABlankObjectAsTheExceptionToHideUserErrors()
{
this.telemetryClient.TrackException((ExceptionTelemetry)null);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];
Assert.IsNotNull(logRecord.Exception);
}

[TestMethod]
public void TrackExceptionUsesErrorLogLevelByDefault()
{
this.telemetryClient.TrackException(new Exception());

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
Assert.AreEqual(LogLevel.Error, this.logItems[0].LogLevel);
}

[TestMethod]
public void TrackExceptionWithExceptionTelemetryRespectsSeverityLevel()
{
var telemetry = new ExceptionTelemetry(new Exception("Critical error"))
{
SeverityLevel = SeverityLevel.Critical
};
this.telemetryClient.TrackException(telemetry);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
Assert.AreEqual(LogLevel.Critical, this.logItems[0].LogLevel);
}

[TestMethod]
public void TrackExceptionWithPropertiesIncludesPropertiesInLogRecord()
{
var properties = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" }
};

this.telemetryClient.TrackException(new Exception("Test"), properties);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];

// Properties should be in the log record attributes
var hasKey1 = false;
var hasKey2 = false;
if (logRecord.Attributes != null)
{
foreach (var attr in logRecord.Attributes)
{
if (attr.Key == "key1" && attr.Value?.ToString() == "value1")
hasKey1 = true;
if (attr.Key == "key2" && attr.Value?.ToString() == "value2")
hasKey2 = true;
}
}

Assert.IsTrue(hasKey1, "Property key1 should be in log record");
Assert.IsTrue(hasKey2, "Property key2 should be in log record");
}

[TestMethod]
public void TrackExceptionWithExceptionTelemetryIncludesProperties()
{
var telemetry = new ExceptionTelemetry(new Exception("Test exception"));
telemetry.Properties["customKey"] = "customValue";

this.telemetryClient.TrackException(telemetry);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];

var hasCustomKey = false;
if (logRecord.Attributes != null)
{
foreach (var attr in logRecord.Attributes)
{
if (attr.Key == "customKey" && attr.Value?.ToString() == "customValue")
{
hasCustomKey = true;
break;
}
}
}

Assert.IsTrue(hasCustomKey, "Custom property should be in log record");
}

[TestMethod]
public void TrackExceptionWithInnerExceptionPreservesInnerException()
{
var innerException = new InvalidOperationException("Inner exception message");
var outerException = new ApplicationException("Outer exception message", innerException);

this.telemetryClient.TrackException(outerException);

this.telemetryClient.Flush();

Assert.AreEqual(1, this.logItems.Count);
var logRecord = this.logItems[0];
Assert.IsNotNull(logRecord.Exception);
Assert.AreEqual("Outer exception message", logRecord.Exception.Message);

// The exception should have inner exception
Assert.IsNotNull(logRecord.Exception.InnerException);
Assert.AreEqual("Inner exception message", logRecord.Exception.InnerException.Message);
}

#endregion

private double ComputeSomethingHeavy()
{
var random = new Random();
double res = 0;
for (int i = 0; i < 10000; i++)
{
res += Math.Sqrt(random.NextDouble());
}

return res;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
namespace Microsoft.ApplicationInsights.DataContracts
{
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Wrapper class for ExceptionDetails"/> that lets user gets/sets TypeName and Message.
/// </summary>
public sealed class ExceptionDetailsInfo
{
// TODO : fix the constructor to set properties

/// <summary>
/// Constructs the instance of <see cref="ExceptionDetailsInfo"/>.
/// </summary>
Expand All @@ -20,29 +17,51 @@ public sealed class ExceptionDetailsInfo
/// <param name="hasFullStack">Indicates that this exception has full stack information.</param>
/// <param name="stack">Exception's stack trace.</param>
/// <param name="parsedStack">Exception's stack.</param>
#pragma warning disable CA1801 // Review unused parameters
public ExceptionDetailsInfo(int id, int outerId, string typeName, string message, bool hasFullStack,
string stack, IEnumerable<StackFrame> parsedStack)
#pragma warning restore CA1801 // Review unused parameters
{
this.Id = id;
this.OuterId = outerId;
this.TypeName = typeName;
this.Message = message;
this.Stack = stack;
this.ParsedStack = parsedStack != null ? new List<StackFrame>(parsedStack) : null;
this.HasFullStack = hasFullStack;
}

/// <summary>
/// Gets or sets type name of the underlying <see cref="System.Exception"/> that this object represents.
/// </summary>
public string TypeName
{
get;
set;
}
public string TypeName { get; set; }

/// <summary>
/// Gets or sets message name of the underlying <see cref="System.Exception"/> that this object represents.
/// </summary>
public string Message
{
get;
set;
}
public string Message { get; set; }

/// <summary>
/// Gets or sets the exception ID.
/// </summary>
internal int Id { get; set; }

/// <summary>
/// Gets or sets the outer exception ID.
/// </summary>
internal int OuterId { get; set; }

/// <summary>
/// Gets or sets the stack trace as a string.
/// </summary>
internal string Stack { get; set; }

/// <summary>
/// Gets or sets the parsed stack frames for the exception.
/// </summary>
internal IList<StackFrame> ParsedStack { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this exception has full stack information.
/// </summary>
internal bool HasFullStack { get; set; }
}
}
Loading
Loading